From 5e20c05fd4e86f143250025f2092acec04dfeda1 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 25 Jan 2024 16:54:43 +0200 Subject: [PATCH 01/82] Fix job event handling when no assignments exist --- .../examples/cvat/exchange-oracle/src/handlers/cvat_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py index 1c2f40c290..5f0e8f3851 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py @@ -29,6 +29,7 @@ def handle_update_job_event(payload: dict) -> None: if "state" in payload.before_update: job_assignments = job.assignments + new_status = JobStatuses(payload.job["state"]) if not job_assignments: logger.warning( @@ -36,7 +37,6 @@ def handle_update_job_event(payload: dict) -> None: "No assignments for this job, ignoring the update" ) else: - new_status = JobStatuses(payload.job["state"]) webhook_time = parse_aware_datetime(payload.job["updated_date"]) webhook_assignee_id = (payload.job["assignee"] or {}).get("id") From 4f453b2c48e3c2cd8487b8ef75dc4f7b6dd86ef3 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 25 Jan 2024 16:58:22 +0200 Subject: [PATCH 02/82] Implement boxes from points task creation --- .../cvat/exchange-oracle/src/chain/escrow.py | 40 +- .../cvat/exchange-oracle/src/chain/kvstore.py | 2 +- .../cvat/exchange-oracle/src/core/config.py | 6 +- .../cvat/exchange-oracle/src/core/manifest.py | 5 + .../cvat/exchange-oracle/src/core/types.py | 1 + .../src/crons/state_trackers.py | 4 +- .../cvat/exchange-oracle/src/cvat/tasks.py | 1082 +++++++++++++++-- .../cvat/exchange-oracle/src/models/cvat.py | 2 +- .../exchange-oracle/src/schemas/webhook.py | 7 +- .../src/services/cloud/client.py | 15 +- .../src/utils/cloud_storage.py | 39 +- .../cvat/exchange-oracle/src/utils/logging.py | 6 + .../tests/api/test_webhook_api.py | 4 +- .../tests/integration/chain/test_escrow.py | 4 +- .../tests/integration/chain/test_kvstore.py | 4 +- .../test_process_recording_oracle_webhooks.py | 2 +- .../utils/datasets/gt_annotations_coco.zip | Bin 1146 -> 0 bytes .../annotations/instances_default.json | 1 - .../tests/utils/datasets/gt_boxes.zip | Bin 0 -> 2851 bytes .../annotations/instances_default.json | 1 + ...01_24_11_59_58_coco keypoints 1.0-good.zip | Bin 0 -> 5859 bytes .../annotations/person_keypoints_default.json | 1 + ...1_10_coco keypoints 1.0-repeated-in-gt.zip | Bin 0 -> 6483 bytes .../annotations/person_keypoints_default.json | 1 + ...18_coco keypoints 1.0-invalid-category.zip | Bin 0 -> 6763 bytes .../annotations/person_keypoints_default.json | 1 + .../manifest_boxes.json} | 0 .../manifest_boxes_from_points_local.json | 24 + .../cvat/recording-oracle/src/chain/escrow.py | 2 +- .../cvat/recording-oracle/src/core/config.py | 12 +- .../crons/process_exchange_oracle_webhooks.py | 6 +- .../tests/integration/chain/test_escrow.py | 2 +- 32 files changed, 1153 insertions(+), 121 deletions(-) delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco.zip delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco/annotations/instances_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes.zip create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good.zip create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt.zip create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt/annotations/person_keypoints_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_03_18_coco keypoints 1.0-invalid-category.zip create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_03_18_coco keypoints 1.0-invalid-category/annotations/person_keypoints_default.json rename packages/examples/cvat/exchange-oracle/tests/utils/{manifest.json => manifests/manifest_boxes.json} (100%) create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/manifests/manifest_boxes_from_points_local.json diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 4c9f13944d..40663be6e5 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -1,3 +1,4 @@ +import datetime import json from typing import List @@ -5,8 +6,29 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient +from src.utils.cloud_storage import parse_bucket_url + def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: + if escrow_address.startswith("test-"): + from human_protocol_sdk.constants import ChainId + + return EscrowData( + chain_id=ChainId(chain_id), + id="test", + address=escrow_address, + amount_paid=10, + balance=10, + count=1, + factory_address="", + launcher="", + status="Pending", + token="HMT", + total_funded_amount=10, + created_at=datetime.datetime(2023, 1, 1), + manifest_url="http://127.0.0.1:9010/manifests/manifest_boxes_from_points_local.json", + ) + escrow = EscrowUtils.get_escrow(chain_id, escrow_address.lower()) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") @@ -40,7 +62,21 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - manifest_content = StorageClient.download_file_from_url(escrow.manifestUrl) + + parsed_url = parse_bucket_url(escrow.manifest_url) + + secure = False + if parsed_url.host_url.startswith("https://"): + host = parsed_url.host_url[len("https://") :] + secure = True + elif parsed_url.host_url.startswith("http://"): + host = parsed_url.host_url[len("http://") :] + else: + host = parsed_url.host_url + + manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( + [parsed_url.path], bucket=parsed_url.bucket_name + )[0] return json.loads(manifest_content.decode("utf-8")) @@ -49,4 +85,4 @@ def get_job_launcher_address(chain_id: int, escrow_address: str) -> str: def get_recording_oracle_address(chain_id: int, escrow_address: str) -> str: - return get_escrow(chain_id, escrow_address).recordingOracle + return get_escrow(chain_id, escrow_address).recording_oracle diff --git a/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py b/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py index 7f86a4904b..07ef28a322 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py @@ -13,7 +13,7 @@ def get_recording_oracle_url(chain_id: int, escrow_address: str) -> str: web3 = get_web3(chain_id) staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.recordingOracle)["webhook_url"] + return staking_client.get_leader(escrow.recording_oracle)["webhook_url"] def get_job_launcher_url(chain_id: int, escrow_address: str) -> str: diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index ae22cbedaa..b37a7a622d 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -98,7 +98,7 @@ class StorageConfig: region = os.environ.get("STORAGE_REGION", "") access_key = os.environ.get("STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") + data_bucket_name = os.environ.get("STORAGE_DATA_BUCKET_NAME", "") secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) @classmethod @@ -112,9 +112,9 @@ def bucket_url(cls): scheme = "https://" if cls.secure else "http://" if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 390a60c5a6..6d6c293b09 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,4 +1,5 @@ from decimal import Decimal +from typing import Optional from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -11,6 +12,10 @@ class DataInfo(BaseModel): "Bucket URL, s3 only, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + points_url: Optional[AnyUrl] = None + "A path to an archive with a set of points in COCO Keypoints format, " + "which provides information about all objects on images" + class LabelInfo(BaseModel): name: str diff --git a/packages/examples/cvat/exchange-oracle/src/core/types.py b/packages/examples/cvat/exchange-oracle/src/core/types.py index d7d151c7b9..c68a3d7684 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/types.py +++ b/packages/examples/cvat/exchange-oracle/src/core/types.py @@ -40,6 +40,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" + image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 8b5d138d0e..90f4509981 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -282,7 +282,7 @@ def retrieve_annotations() -> None: existing_storage_files = set( f.key for f in storage_client.list_files( - StorageConfig.results_bucket_name, + StorageConfig.data_bucket_name, path=compose_output_annotation_filename( project.escrow_address, project.chain_id, @@ -295,7 +295,7 @@ def retrieve_annotations() -> None: continue storage_client.create_file( - StorageConfig.results_bucket_name, + StorageConfig.data_bucket_name, compose_output_annotation_filename( project.escrow_address, project.chain_id, diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py b/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py index c36e088155..a9df84d344 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py @@ -1,32 +1,50 @@ +from __future__ import annotations + import os import random +import uuid +from contextlib import ExitStack +from itertools import groupby +from logging import Logger +from math import ceil +from pathlib import Path from tempfile import TemporaryDirectory -from typing import List +from typing import Dict, List, Sequence, Tuple, Union, cast +import attrs +import cv2 import datumaro as dm -from datumaro.util import take_by -from datumaro.util.image import IMAGE_EXTENSIONS +import numpy as np +from attrs import frozen +from datumaro.util import dump_json, take_by +from datumaro.util.image import IMAGE_EXTENSIONS, decode_image, encode_image import src.cvat.api_calls as cvat_api import src.services.cloud as cloud_service import src.services.cvat as db_service from src.chain.escrow import get_escrow_manifest +from src.core.config import Config from src.core.manifest import TaskManifest -from src.core.types import CvatLabelType, TaskStatus, TaskType +from src.core.types import CloudProviders, CvatLabelType, TaskStatus, TaskType from src.db import SessionLocal +from src.log import ROOT_LOGGER_NAME +from src.services.cloud.client import S3Client from src.utils.assignments import parse_manifest -from src.utils.cloud_storage import compose_bucket_url, parse_bucket_url +from src.utils.cloud_storage import BucketConfig, compose_bucket_url, parse_bucket_url +from src.utils.logging import NullLogger, get_function_logger LABEL_TYPE_MAPPING = { TaskType.image_label_binary: CvatLabelType.tag, TaskType.image_points: CvatLabelType.points, TaskType.image_boxes: CvatLabelType.rectangle, + TaskType.image_boxes_from_points: CvatLabelType.rectangle, } DM_DATASET_FORMAT_MAPPING = { TaskType.image_label_binary: "cvat_images", TaskType.image_points: "coco_person_keypoints", TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", } DM_GT_DATASET_FORMAT_MAPPING = { @@ -34,8 +52,908 @@ TaskType.image_label_binary: "cvat_images", TaskType.image_points: "coco_instances", TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", +} + +CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { + CloudProviders.aws: "AWS_S3_BUCKET", + CloudProviders.gcs: "GOOGLE_CLOUD_STORAGE", } +module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" + + +class BoxesFromPointsTaskBuilder: + class _NotConfigured: + def __bool__(self) -> bool: + return False + + _not_configured = _NotConfigured() + + class DatasetValidationError(Exception): + pass + + class MismatchingAnnotations(DatasetValidationError): + pass + + class TooFewSamples(DatasetValidationError): + pass + + class TooManyBoxesDiscarded(DatasetValidationError): + pass + + class InvalidCategories(DatasetValidationError): + pass + + class InvalidImageInfo(DatasetValidationError): + pass + + max_discarded_threshold = 0.5 + "The maximum allowed percent of discarded " + "GT boxes, points, or samples for successful job launch" + + def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): + self.exit_stack = ExitStack() + self.manifest = manifest + self.escrow_address = escrow_address + self.chain_id = chain_id + + self.logger: Logger = NullLogger() + + self.input_gt_data: Union[bytes, self._NotConfigured] = self._not_configured + self.input_points_data: Union[bytes, self._NotConfigured] = self._not_configured + + # Computed values + self.input_filenames: Union[self._NotConfigured, Sequence[str]] = self._not_configured + self.input_gt_dataset: Union[self._NotConfigured, dm.Dataset] = self._not_configured + self.input_points_dataset: Union[self._NotConfigured, dm.Dataset] = self._not_configured + + # Output values + self.gt_dataset: Union[dm.Dataset, self._NotConfigured] = self._not_configured + + self.bbox_point_mapping: Union[Dict[int, int], self._NotConfigured] = self._not_configured + "bbox_id -> point_id" + + self.roi_size_estimations: Union[ + Dict[int, Tuple[float, float]], self._NotConfigured + ] = self._not_configured + "label_id -> (rel. w, rel. h)" + + self.rois: Union[Sequence[self.RoiInfo], self._NotConfigured] = self._not_configured + self.roi_filenames: Union[Dict[int, str], self._NotConfigured] = self._not_configured + + self.job_layout: Union[Sequence[Sequence[str]], self._NotConfigured] = self._not_configured + "File lists per CVAT job" + + self.label_configuration: Union[Sequence[dict], self._NotConfigured] = self._not_configured + + # Configuration / constants + # TODO: consider WebP if produced files are too big + self.roi_file_ext = ".png" # supposed to be lossless and reasonably compressing + "File extension for RoI images, with leading dot (.) included" + + self.sample_error_display_threshold = 5 + "The maximum number of rendered list items in a message" + + self.roi_size_mult = 1.1 + "Additional point ROI size multiplier" + + self.points_format = "coco_person_keypoints" + + self.embed_point_in_roi_image = True + "Put a small point into the extracted RoI images for the original point" + + self.embedded_point_radius = 15 + self.min_embedded_point_radius_percent = 0.005 + self.max_embedded_point_radius_percent = 0.01 + self.embedded_point_color = (0, 255, 255) + + self.oracle_data_bucket = BucketConfig.from_url(Config.storage_config.bucket_url()) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + self.oracle_data_bucket_prefix = f"{self.escrow_address}@{self.chain_id}" + "Filename prefix" + + self.min_class_samples_for_roi_estimation = 100 + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + self.close() + + def close(self): + self.exit_stack.close() + + def set_logger(self, logger: Logger): + # TODO: add escrow info into messages + self.logger = logger + return self + + def _download_input_data(self): + data_bucket = BucketConfig.from_url(self.manifest.data.data_url) + gt_bucket = BucketConfig.from_url(self.manifest.validation.gt_url) + points_bucket = BucketConfig.from_url(self.manifest.data.points_url) + + data_storage_client = self._make_cloud_storage_client(data_bucket) + gt_storage_client = self._make_cloud_storage_client(gt_bucket) + points_storage_client = self._make_cloud_storage_client(points_bucket) + + data_filenames = [ + file_info.key + for file_info in data_storage_client.list_files( + data_bucket.url.bucket_name, + data_bucket.url.path, + ) + ] + self.input_filenames = filter_image_files(data_filenames) + + self.input_gt_data = gt_storage_client.download_fileobj( + gt_bucket.url.bucket_name, + gt_bucket.url.path, + ) + + self.input_points_data = points_storage_client.download_fileobj( + points_bucket.url.bucket_name, + points_bucket.url.path, + ) + + def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: + temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) + + annotation_filename = os.path.join(temp_dir, "annotations.json") + with open(annotation_filename, "wb") as f: + f.write(annotation_file_data) + + return dm.Dataset.import_from(annotation_filename, format=dataset_format) + + def _parse_gt(self): + assert self.input_gt_data is not self._not_configured + + self.input_gt_dataset = self._parse_dataset( + self.input_gt_data, + dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], + ) + + def _parse_points(self): + assert self.input_points_data is not self._not_configured + + self.input_points_dataset = self._parse_dataset( + self.input_points_data, dataset_format=self.points_format + ) + + def _validate_gt_labels(self): + gt_labels = set( + label.name + for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if gt_labels - manifest_labels: + raise self.DatasetValidationError( + "GT labels do not match job labels. Unknown labels: {}".format( + self._format_list(gt_labels - manifest_labels), + ) + ) + + self.input_gt_dataset.transform( + "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self.input_gt_dataset.init_cache() + + def _validate_gt_filenames(self): + gt_filenames = set(s.id + s.media.ext for s in self.input_gt_dataset) + + known_data_filenames = set(self.input_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + extra_gt = list(map(os.path.basename, gt_filenames - matched_gt_filenames)) + + raise self.MismatchingAnnotations( + "Failed to find several validation samples in the dataset files: {}".format( + self._format_list(extra_gt) + ) + ) + + if len(gt_filenames) < self.manifest.validation.val_size: + raise self.TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {self.manifest.validation.val_size} required." + ) + + def _validate_gt(self): + assert self.input_filenames is not self._not_configured + assert self.input_gt_dataset is not self._not_configured + + self._validate_gt_filenames() + self._validate_gt_labels() + + def _format_list( + self, items: Sequence[str], *, max_items: int = None, separator: str = ", " + ) -> str: + if max_items is None: + max_items = self.sample_error_display_threshold + + remainder_count = len(items) - max_items + return "{}{}".format( + separator.join(items[:max_items]), + f"(and {remainder_count} more)" if remainder_count > 0 else "", + ) + + def _validate_points_categories(self): + invalid_point_categories_messages = [] + points_dataset_categories = self.input_points_dataset.categories() + points_dataset_label_cat: dm.LabelCategories = points_dataset_categories[ + dm.AnnotationType.label + ] + for category_id, category in points_dataset_categories[ + dm.AnnotationType.points + ].items.items(): + if len(category.labels) != 1: + invalid_point_categories_messages.append( + "Category '{}' (#{}): {}".format( + points_dataset_label_cat[category_id].name, + category_id, + f"too many skeleton points ({len(category.labels)}), only 1 expected", + ) + ) + + if invalid_point_categories_messages: + raise self.InvalidCategories( + "Invalid categories in the input point annotations: {}".format( + self._format_list(invalid_point_categories_messages, separator="; ") + ) + ) + + points_labels = set(label.name for label in points_dataset_label_cat if not label.parent) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if manifest_labels != points_labels: + raise self.DatasetValidationError("Point labels do not match job labels") + + self.input_points_dataset.transform( + "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self.input_points_dataset.init_cache() + + def _validate_points_filenames(self): + points_filenames = set() + filenames_with_invalid_points = set() + for sample in self.input_points_dataset: + sample_id = sample.id + sample.media.ext + points_filenames.add(sample_id) + + skeletons = [a for a in sample.annotations if isinstance(a, dm.Skeleton)] + for skeleton in skeletons: + if len(skeleton.elements) != 1: + filenames_with_invalid_points.add(sample_id) + break + + if filenames_with_invalid_points: + raise self.MismatchingAnnotations( + "Some images have invalid points: {}".format( + self._format_list(filenames_with_invalid_points) + ) + ) + + known_data_filenames = set(self.input_filenames) + matched_points_filenames = points_filenames.intersection(known_data_filenames) + + if len(known_data_filenames) != len(matched_points_filenames): + missing_point_samples = list( + map(os.path.basename, known_data_filenames - matched_points_filenames) + ) + extra_point_samples = list( + map(os.path.basename, points_filenames - matched_points_filenames) + ) + + raise self.MismatchingAnnotations( + "Mismatching points info and input files: {}".format( + "; ".join( + "{} missing points".format(self._format_list(missing_point_samples)), + "{} extra points".format(self._format_list(extra_point_samples)), + ) + ) + ) + + def _validate_points_annotations(self): + label_cat: dm.LabelCategories = self.input_points_dataset.categories()[ + dm.AnnotationType.label + ] + + excluded_samples = [] + for sample in self.input_points_dataset: + image_h, image_w = sample.image.size + + for skeleton in sample.annotations: + # Could fail on this as well + if not isinstance(skeleton, dm.Skeleton): + continue + + point = skeleton.elements[0] + px, py = point.points[:2] + + if px < 0 or py < 0 or px > image_w or py > image_h: + message = ( + "Sample '{}': point #{} ({}) skipped - " + "coordinates are outside image".format( + sample.id, skeleton.id, label_cat[skeleton.label].name + ) + ) + excluded_samples.append(((sample.id, sample.subset), message)) + + if len(excluded_samples) > len(self.input_points_dataset) * self.max_discarded_threshold: + raise self.DatasetValidationError( + "Too many samples discarded, canceling job creation. Errors: {}".format( + self._format_list([message for _, message in excluded_samples]) + ) + ) + + for excluded_sample, _ in excluded_samples: + self.input_points_dataset.remove(*excluded_sample) + + if excluded_samples: + self.logger.info( + "Some samples were excluded due to errors found: {}".format( + self._format_list([m for _, m in excluded_samples], separator="\n") + ) + ) + + def _validate_points(self): + assert self.input_filenames is not self._not_configured + assert self.input_points_dataset is not self._not_configured + + self._validate_points_categories() + self._validate_points_filenames() + self._validate_points_annotations() + + @staticmethod + def _is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: + return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) + + def _prepare_gt(self): + assert self.input_filenames is not self._not_configured + assert self.input_points_dataset is not self._not_configured + assert self.input_gt_dataset is not self._not_configured + assert [ + label.name for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + ] == [label.name for label in self.manifest.annotation.labels] + assert [ + label.name + for label in self.input_points_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + gt_dataset = dm.Dataset(categories=self.input_gt_dataset.categories(), media_type=dm.Image) + + gt_label_cat: dm.LabelCategories = self.input_gt_dataset.categories()[ + dm.AnnotationType.label + ] + + excluded_boxes_messages = [] + total_boxes = 0 + gt_per_class = {} + + bbox_point_mapping = {} # bbox id -> point id + for gt_sample in self.input_gt_dataset: + points_sample = self.input_points_dataset.get(gt_sample.id, gt_sample.subset) + assert points_sample + + image_h, image_w = points_sample.image.size + + gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] + input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] + + # Samples without boxes are allowed + if not gt_boxes: + continue + + total_boxes += len(gt_boxes) + + matched_boxes = [] + visited_skeletons = set() + for gt_bbox in gt_boxes: + gt_bbox_id = gt_bbox.id + + if ( + gt_bbox.x < 0 + or gt_bbox.y < 0 + or gt_bbox.x + gt_bbox.w > image_w + or gt_bbox.y + gt_bbox.h > image_h + ): + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) - " + "coordinates are outside image. The image will be skipped".format( + gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name + ) + ) + matched_boxes = [] + break + + if len(visited_skeletons) == len(gt_boxes): + # Handle unmatched boxes + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) skipped - " + "no matching points found".format( + gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name + ) + ) + continue + + matched_skeletons: List[dm.Skeleton] = [] + for input_skeleton in input_skeletons: + skeleton_id = input_skeleton.id + if skeleton_id in visited_skeletons: + continue + + input_point = input_skeleton.elements[0] + if not self._is_point_in_bbox(*input_point.points[0:2], bbox=gt_bbox): + continue + + if input_skeleton.label != gt_bbox.label: + continue + + matched_skeletons.append(input_skeleton) + visited_skeletons.add(skeleton_id) + + if len(matched_skeletons) > 1: + # Handle ambiguous matches + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) skipped - " + "too many matching points ({}) found".format( + gt_sample.id, + gt_bbox_id, + gt_label_cat[gt_bbox.label].name, + len(matched_skeletons), + ) + ) + continue + elif len(matched_skeletons) == 0: + # Handle unmatched boxes + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) skipped - " + "no matching points found".format( + gt_sample.id, + gt_bbox_id, + gt_label_cat[gt_bbox.label].name, + ) + ) + continue + + matched_boxes.append(gt_bbox) + bbox_point_mapping[gt_bbox_id] = matched_skeletons[0].id + + if not matched_boxes: + continue + + gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) + + if len(bbox_point_mapping) < (1 - self.max_discarded_threshold) * total_boxes: + raise self.TooManyBoxesDiscarded( + "Too many GT boxes discarded ({} out of {}). " + "Please make sure each GT box matches exactly 1 point".format( + total_boxes - len(bbox_point_mapping), total_boxes + ) + ) + elif excluded_boxes_messages: + self.logger.info(self._format_list(excluded_boxes_messages, separator="\n")) + + gt_labels_without_anns = [ + gt_label_cat[label_id] + for label_id, label_count in gt_per_class.items() + if not label_count + ] + if gt_labels_without_anns: + raise self.DatasetValidationError( + "No matching GT boxes/points annotations found for some classes: {}".format( + self._format_list(gt_labels_without_anns) + ) + ) + + self.gt_dataset = gt_dataset + self.bbox_point_mapping = bbox_point_mapping + + def _estimate_roi_sizes(self): + assert self.gt_dataset is not self._not_configured + assert [label.name for label in self.gt_dataset.categories()[dm.AnnotationType.label]] == [ + label.name for label in self.manifest.annotation.labels + ] + + bbox_sizes_per_label = {} + gt_counts_per_label = {} + for sample in self.gt_dataset: + image_h, image_w = self.input_points_dataset.get(sample.id, sample.subset).image.size + + for gt_bbox in sample.annotations: + gt_bbox = cast(dm.Bbox, gt_bbox) + bbox_sizes_per_label.setdefault(gt_bbox.label, []).append( + ( + gt_bbox.w / image_w, + gt_bbox.h / image_h, + ) + ) + + gt_counts_per_label[gt_bbox.label] = gt_counts_per_label.get(gt_bbox.label, 0) + 1 + + # Consider bbox sides as normally-distributed random variables, estimate sigmas + # For big enough datasets, it should be reasonable approximation + # (due to the central limit theorem). This can work bad for small datasets, + # so we only do this if there are enough class samples. + classes_with_default_roi = [] + roi_size_estimations_per_label = {} # label id -> (w, h) + for label_id, label_sizes in bbox_sizes_per_label.items(): + if gt_counts_per_label[label_id] < self.min_class_samples_for_roi_estimation: + classes_with_default_roi.append(label_id) + estimated_size = (1, 1) + else: + mean_size = np.average(label_sizes, axis=0) + sigma = np.sqrt(np.var(label_sizes, axis=0)) + estimated_size = (mean_size + 3 * sigma) * self.roi_size_mult + + roi_size_estimations_per_label[label_id] = estimated_size + + if classes_with_default_roi: + label_cat = self.gt_dataset.categories()[dm.AnnotationType.label] + self.logger.debug( + "Some classes will use the full image instead of RoI" + "- too few GT provided: {}".format( + self._format_list( + [label_cat[label_id].name for label_id in classes_with_default_roi] + ) + ) + ) + + self.roi_size_estimations = roi_size_estimations_per_label + + @frozen + class RoiInfo: + point_id: int + image_id: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + def _prepare_roi_info(self): + assert self.gt_dataset is not self._not_configured + assert self.roi_size_estimations is not self._not_configured + assert self.input_points_dataset is not self._not_configured + + rois: List[self.RoiInfo] = [] + for sample in self.input_points_dataset: + for skeleton in sample.annotations: + if not isinstance(skeleton, dm.Skeleton): + continue + + point_label_id = skeleton.label + original_point_x, original_point_y = skeleton.elements[0].points[:2] + original_point_x = int(original_point_x) + original_point_y = int(original_point_y) + + image_h, image_w = sample.image.size + + roi_est_w, roi_est_h = self.roi_size_estimations[point_label_id] + roi_est_w *= image_w + roi_est_h *= image_h + + roi_left = max(0, original_point_x - int(roi_est_w / 2)) + roi_top = max(0, original_point_y - int(roi_est_h / 2)) + roi_right = min(image_w, original_point_x + ceil(roi_est_w / 2)) + roi_bottom = min(image_h, original_point_y + ceil(roi_est_h / 2)) + + roi_w = roi_right - roi_left + roi_h = roi_bottom - roi_top + + new_point_x = original_point_x - roi_left + new_point_y = original_point_y - roi_top + + rois.append( + self.RoiInfo( + point_id=skeleton.id, + image_id=sample.attributes["id"], + point_x=new_point_x, + point_y=new_point_y, + roi_x=roi_left, + roi_y=roi_top, + roi_w=roi_w, + roi_h=roi_h, + ) + ) + + self.rois = rois + + def _mangle_filenames(self): + """ + Mangle filenames in the dataset to make them less recognizable by annotators + and hide private dataset info + """ + assert self.rois is not self._not_configured + + # TODO: maybe add different names for the same GT images in + # different jobs to make them even less recognizable + self.roi_filenames = { + roi.point_id: str(uuid.uuid4()) + self.roi_file_ext for roi in self.rois + } + + def _prepare_job_layout(self): + # Make job layouts wrt. manifest params + # 1 job per task as CVAT can't repeat images in jobs, but GTs can repeat in the dataset + + assert self.rois is not self._not_configured + assert self.bbox_point_mapping is not self._not_configured + + gt_point_ids = set(self.bbox_point_mapping.values()) + gt_filenames = [self.roi_filenames[point_id] for point_id in gt_point_ids] + + data_filenames = [ + fn for point_id, fn in self.roi_filenames.items() if not point_id in gt_point_ids + ] + random.shuffle(data_filenames) + + job_layout = [] + for data_samples in take_by(data_filenames, self.manifest.annotation.job_size): + gt_samples = random.sample(gt_filenames, k=self.manifest.validation.val_size) + job_samples = list(data_samples) + list(gt_samples) + random.shuffle(job_samples) + job_layout.append(job_samples) + + self.job_layout = job_layout + + def _prepare_label_configuration(self): + self.label_configuration = make_label_configuration(self.manifest) + + class TaskParamsLayout: + GT_FILENAME = "gt.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + def _upload_config(self): + # TODO: maybe extract into a separate function / class / library, + # extract constants, serialization methods return TaskConfig from build() + + layout = self.TaskParamsLayout + + file_list = [] + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + self.gt_dataset.export(gt_dataset_dir, self.points_format) + file_list.append( + ( + ( + Path(gt_dataset_dir) / "annotations" / "person_keypoints_default.json" + ).read_bytes(), + layout.GT_FILENAME, + ) + ) + + bbox_point_mapping_file = dump_json( + {str(k): str(v) for k, v in self.bbox_point_mapping.items()} + ) + file_list.append((bbox_point_mapping_file, layout.BBOX_POINT_MAPPING_FILENAME)) + + rois_file = dump_json([roi_info.asdict() for roi_info in self.rois]) + file_list.append((rois_file, layout.ROI_INFO_FILENAME)) + + roi_filenames_file = dump_json({str(k): v for k, v in self.roi_filenames.items()}) + file_list.append((roi_filenames_file, layout.ROI_FILENAMES_FILENAME)) + + client = self._make_cloud_storage_client(self.oracle_data_bucket) + bucket_name = self.oracle_data_bucket.url.bucket_name + prefix = self.oracle_data_bucket_prefix + for file_data, filename in file_list: + client.create_file(bucket_name, os.path.join(prefix, filename), file_data) + + def _draw_roi_point(self, roi_pixels: np.ndarray, roi_info: RoiInfo) -> np.ndarray: + center = (roi_info.point_x, roi_info.point_y) + + roi_r = (roi_info.roi_w**2 + roi_info.roi_h**2) ** 0.5 / 2 + point_size = int( + min( + self.max_embedded_point_radius_percent * roi_r, + max(self.embedded_point_radius, self.min_embedded_point_radius_percent * roi_r), + ) + ) + + roi_pixels = roi_pixels.copy() + roi_pixels = cv2.circle( + roi_pixels, + center, + point_size + 1, + (255, 255, 255), + cv2.FILLED, + ) + roi_pixels = cv2.circle( + roi_pixels, + center, + point_size, + self.embedded_point_color, + cv2.FILLED, + ) + + return roi_pixels + + def _extract_and_upload_rois(self): + # TODO: maybe optimize via splitting into separate threads (downloading, uploading, processing) + # Watch for the memory used, as the whole dataset can be quite big (gigabytes, terabytes) + # Consider also packing RoIs cut into archives + assert self.input_points_dataset is not self._not_configured + assert self.rois is not self._not_configured + assert self.input_filenames is not self._not_configured + assert self.roi_filenames is not self._not_configured + + src_bucket = BucketConfig.from_url(self.manifest.data.data_url) + src_prefix = "" + dst_bucket = self.oracle_data_bucket + dst_prefix = self.oracle_data_bucket_prefix + + src_client = self._make_cloud_storage_client(src_bucket) + dst_client = self._make_cloud_storage_client(dst_bucket) + + image_id_to_filename = { + sample.attributes["id"]: sample.image.path for sample in self.input_points_dataset + } + + filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} + + _roi_key = lambda e: e.image_id + rois_by_image: Dict[str, self.RoiInfo] = { + image_id_to_filename[image_id]: list(g) + for image_id, g in groupby(sorted(self.rois, key=_roi_key), key=_roi_key) + } + + for filename in self.input_filenames: + image_roi_infos = rois_by_image.get(filename, []) + if not image_roi_infos: + continue + + image_bytes = src_client.download_fileobj( + src_bucket.url.bucket_name, os.path.join(src_prefix, filename) + ) + image_pixels = decode_image(image_bytes) + + sample = filename_to_sample[filename] + if tuple(sample.image.size) != tuple(image_pixels.shape[:2]): + # TODO: maybe rois should be regenerated instead + # Option 2: accumulate errors, fail when some threshold is reached + # Option 3: add special handling for cases when image is only rotated (exif etc.) + raise self.InvalidImageInfo( + f"Sample '{filename}': invalid size provided in the point annotations" + ) + + image_rois = {} + for roi_info in image_roi_infos: + roi_info = cast(self.RoiInfo, roi_info) + roi_pixels = image_pixels[ + roi_info.roi_y : roi_info.roi_y + roi_info.roi_h, + roi_info.roi_x : roi_info.roi_x + roi_info.roi_w, + ] + + if self.embed_point_in_roi_image: + roi_pixels = self._draw_roi_point(roi_pixels, roi_info) + + roi_filename = self.roi_filenames[roi_info.point_id] + roi_bytes = encode_image(roi_pixels, os.path.splitext(roi_filename)[-1]) + + image_rois[roi_filename] = roi_bytes + + for roi_filename, roi_bytes in image_rois.items(): + dst_client.create_file( + dst_bucket.url.bucket_name, os.path.join(dst_prefix, roi_filename), roi_bytes + ) + + def _create_on_cvat(self): + assert self.job_layout is not self._not_configured + assert self.label_configuration is not self._not_configured + + input_data_bucket = BucketConfig.from_url(self.manifest.data.data_url) + + oracle_bucket = self.oracle_data_bucket + oracle_bucket_prefix = self.oracle_data_bucket_prefix + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[oracle_bucket.provider], + oracle_bucket.url.host_url.replace( + # TODO: remove mock + "127.0.0.1", + "172.22.0.1", + ), + oracle_bucket.url.bucket_name, + # TODO: add + # credentials=... + ) + + # Create a project + project = cvat_api.create_project( + self.escrow_address, + labels=self.label_configuration, + user_guide=self.manifest.annotation.user_guide, + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + input_data_bucket = parse_bucket_url(self.manifest.data.data_url) + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.bucket_name, + bucket_host=input_data_bucket.host_url, + provider=input_data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images(session, project.id, list(self.roi_filenames.values())) + + for job_filenames in self.job_layout: + task = cvat_api.create_task(project.id, self.escrow_address) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=[os.path.join(oracle_bucket_prefix, fn) for fn in job_filenames], + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + def _make_cloud_storage_client(self, bucket_info: BucketConfig) -> S3Client: + match bucket_info.provider: + case CloudProviders.aws: + client_kwargs = {} + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + client = S3Client(bucket_info.url.host_url, **client_kwargs) + case _: + raise Exception("Unsupported cloud provider") + + return client + + def build(self): + self._download_input_data() + self._parse_gt() + self._parse_points() + self._validate_gt() + self._validate_points() + + # Task configuration creation + self._prepare_gt() + self._estimate_roi_sizes() + self._prepare_roi_info() + self._mangle_filenames() + self._prepare_label_configuration() + self._prepare_job_layout() + + # Data preparation + self._extract_and_upload_rois() + self._upload_config() + + self._create_on_cvat() + def get_gt_filenames( gt_file_data: bytes, data_filenames: List[str], *, manifest: TaskManifest @@ -109,94 +1027,104 @@ def make_label_configuration(manifest: TaskManifest) -> List[dict]: return [ { "name": label.name, - "type": LABEL_TYPE_MAPPING.get(manifest.annotation.type).value, + "type": LABEL_TYPE_MAPPING[manifest.annotation.type].value, } for label in manifest.annotation.labels ] def create_task(escrow_address: str, chain_id: int) -> None: + logger = get_function_logger(module_logger) + manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) - parsed_data_bucket_url = parse_bucket_url(manifest.data.data_url) - data_cloud_provider = parsed_data_bucket_url.provider - data_bucket_host = parsed_data_bucket_url.host_url - data_bucket_name = parsed_data_bucket_url.bucket_name - data_bucket_path = parsed_data_bucket_url.path - - # Validate and parse GT - parsed_gt_bucket_url = parse_bucket_url(manifest.validation.gt_url) - gt_bucket_host = parsed_gt_bucket_url.host_url - gt_bucket_name = parsed_gt_bucket_url.bucket_name - gt_filename = parsed_gt_bucket_url.path - - # Register cloud storage on CVAT to pass user dataset - cloud_storage = cvat_api.create_cloudstorage( - data_cloud_provider, data_bucket_host, data_bucket_name - ) - - # Task configuration creation - data_filenames = cloud_service.list_files( - data_bucket_host, - data_bucket_name, - data_bucket_path, - ) - data_filenames = filter_image_files(data_filenames) - - gt_file_data = cloud_service.download_file( - gt_bucket_host, - gt_bucket_name, - gt_filename, - ) - gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) - - job_configuration = make_job_configuration(data_filenames, gt_filenames, manifest=manifest) - label_configuration = make_label_configuration(manifest) - - # Create a project - project = cvat_api.create_project( - escrow_address, - labels=label_configuration, - user_guide=manifest.annotation.user_guide, - ) - - # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) + if manifest.annotation.type in [ + TaskType.image_boxes, + TaskType.image_points, + TaskType.image_label_binary, + ]: + data_bucket_address = parse_bucket_url(manifest.data.data_url) + gt_bucket_address = parse_bucket_url(manifest.validation.gt_url) - with SessionLocal.begin() as session: - db_service.create_project( - session, - project.id, - cloud_storage.id, - manifest.annotation.type, - escrow_address, - chain_id, - compose_bucket_url( - data_bucket_name, - bucket_host=data_bucket_host, - provider=data_cloud_provider, - ), - cvat_webhook_id=webhook.id, + # Task configuration creation + data_filenames = cloud_service.list_files( + data_bucket_address.host_url, + data_bucket_address.bucket_name, + data_bucket_address.path, ) - db_service.add_project_images(session, project.id, data_filenames) + data_filenames = filter_image_files(data_filenames) - for job_filenames in job_configuration: - task = cvat_api.create_task(project.id, escrow_address) + gt_file_data = cloud_service.download_file( + gt_bucket_address.host_url, + gt_bucket_address.bucket_name, + gt_bucket_address.path, + ) - with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + # Validate and parse GT + gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) + + job_configuration = make_job_configuration(data_filenames, gt_filenames, manifest=manifest) + label_configuration = make_label_configuration(manifest) - # Actual task creation in CVAT takes some time, so it's done in an async process. - # The task will be created in DB once 'update:task' or 'update:job' webhook is received. - cvat_api.put_task_data( - task.id, - cloud_storage.id, - filenames=job_filenames, - sort_images=False, + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[data_bucket_address.provider], + data_bucket_address.host_url, + data_bucket_address.bucket_name, ) + # Create a project + project = cvat_api.create_project( + escrow_address, + labels=label_configuration, + user_guide=manifest.annotation.user_guide, + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) + db_service.create_project( + session, + project.id, + cloud_storage.id, + manifest.annotation.type, + escrow_address, + chain_id, + compose_bucket_url( + data_bucket_address.bucket_name, + bucket_host=data_bucket_address.host_url, + provider=data_bucket_address.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images(session, project.id, data_filenames) + + for job_filenames in job_configuration: + task = cvat_api.create_task(project.id, escrow_address) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=job_filenames, + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + elif manifest.annotation.type in [TaskType.image_boxes_from_points]: + with BoxesFromPointsTaskBuilder(manifest, escrow_address, chain_id) as task_builder: + task_builder.set_logger(logger) + task_builder.build() + + else: + raise Exception(f"Unsupported task type {manifest.annotation.type}") def remove_task(escrow_address: str) -> None: diff --git a/packages/examples/cvat/exchange-oracle/src/models/cvat.py b/packages/examples/cvat/exchange-oracle/src/models/cvat.py index 2ffed4ccd6..0ab9d59224 100644 --- a/packages/examples/cvat/exchange-oracle/src/models/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/models/cvat.py @@ -28,7 +28,7 @@ class Project(Base): job_type = Column(String, Enum(TaskType), nullable=False) escrow_address = Column(String(42), unique=True, nullable=False) chain_id = Column(Integer, Enum(Networks), nullable=False) - bucket_url = Column(String, nullable=False) + bucket_url = Column(String, nullable=False) # TODO: consider removal created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) cvat_webhook_id = Column(Integer, nullable=True) diff --git a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py index ac3e7afcf4..20384c3e9d 100644 --- a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py @@ -14,9 +14,10 @@ class OracleWebhook(BaseModel): event_data: Optional[dict] = None timestamp: Optional[datetime] = None # TODO: remove optional - @validator("escrow_address", allow_reuse=True) - def validate_escrow_(cls, value): - return validate_address(value) + # TODO: remove mock + # @validator("escrow_address", allow_reuse=True) + # def validate_escrow_(cls, value): + # return validate_address(value) # pylint: disable=too-few-public-methods class Config: diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index 2c6101a573..3d819d8512 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -4,6 +4,7 @@ from io import BytesIO from typing import List, Optional +from urllib.parse import unquote import boto3 from botocore.exceptions import ClientError @@ -22,7 +23,7 @@ def __init__( "s3", **(dict(aws_access_key_id=access_key) if access_key else {}), **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=endpoint_url, + endpoint_url=unquote(endpoint_url), ) self.resource = s3 @@ -32,14 +33,14 @@ def __init__( self.client.meta.events.register("choose-signer.s3.*", disable_signing) def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=bucket, Key=filename) + self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=bucket, Key=filename) + self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) def file_exists(self, bucket: str, filename: str) -> bool: try: - self.client.head_object(Bucket=bucket, Key=filename) + self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) return True except ClientError as e: if e.response["Error"]["Code"] == "404": @@ -49,13 +50,13 @@ def file_exists(self, bucket: str, filename: str) -> bool: def download_fileobj(self, bucket: str, key: str) -> bytes: with BytesIO() as data: - self.client.download_fileobj(Bucket=bucket, Key=key, Fileobj=data) + self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) return data.getvalue() def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(bucket).objects + objects = self.resource.Bucket(unquote(bucket)).objects if path: - objects = objects.filter(Prefix=path.strip("/\\") + "/") + objects = objects.filter(Prefix=unquote(path).strip("/\\") + "/") else: objects = objects.all() return list(objects) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py index 3418fde5bc..0e0d968887 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from typing import Optional from urllib.parse import urlparse @@ -9,12 +11,37 @@ @dataclass class ParsedBucketUrl: - provider: str + provider: CloudProviders host_url: str bucket_name: str path: str +class BucketCredentials: + pass + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +@dataclass +class BucketConfig: + provider: CloudProviders + url: ParsedBucketUrl + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_url(cls, url: str) -> BucketConfig: + return cls.from_parsed_url(parse_bucket_url(url)) + + @classmethod + def from_parsed_url(cls, parsed_url: ParsedBucketUrl) -> BucketConfig: + return BucketConfig(parsed_url.provider, url=parsed_url) + + DEFAULT_S3_HOST = "s3.amazonaws.com" @@ -24,7 +51,7 @@ def parse_bucket_url(data_url: str) -> ParsedBucketUrl: if parsed_url.netloc.endswith(DEFAULT_S3_HOST): # AWS S3 bucket return ParsedBucketUrl( - provider=CloudProviders.aws.value, + provider=CloudProviders.aws, host_url=f"https://{DEFAULT_S3_HOST}", bucket_name=parsed_url.netloc.split(".")[0], path=parsed_url.path.lstrip("/"), @@ -32,7 +59,7 @@ def parse_bucket_url(data_url: str) -> ParsedBucketUrl: # elif parsed_url.netloc.endswith("storage.googleapis.com"): # # Google Cloud Storage (GCS) bucket # return ParsedBucketUrl( - # provider=CloudProviders.gcs.value, + # provider=CloudProviders.gcs, # bucket_name=parsed_url.netloc.split(".")[0], # ) elif Config.features.enable_custom_cloud_host: @@ -45,7 +72,7 @@ def parse_bucket_url(data_url: str) -> ParsedBucketUrl: path = parsed_url.path.lstrip("/") return ParsedBucketUrl( - provider=CloudProviders.aws.value, + provider=CloudProviders.aws, host_url=f"{parsed_url.scheme}://{host}", bucket_name=bucket_name, path=path, @@ -58,7 +85,7 @@ def compose_bucket_url( bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None ) -> str: match provider: - case CloudProviders.aws.value: + case CloudProviders.aws: return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs.value: + case CloudProviders.gcs: return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" diff --git a/packages/examples/cvat/exchange-oracle/src/utils/logging.py b/packages/examples/cvat/exchange-oracle/src/utils/logging.py index d9ce28d024..146f6c219b 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/logging.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/logging.py @@ -20,3 +20,9 @@ def get_function_logger( function_name = current_function_name(depth=2) return parent_logger.getChild(function_name) + + +class NullLogger(logging.Logger): + def __init__(self, name: str = "", level=0) -> None: + super().__init__(name, level) + self.disabled = True diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py index 56958c1f32..64164fcf11 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py @@ -23,7 +23,7 @@ def test_incoming_webhook_200(client: TestClient) -> None: mock_get_web3.return_value = Web3(HTTPProvider(Networks.localhost)) mock_escrow = Mock() mock_escrow.launcher = JOB_LAUNCHER - mock_escrow.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow.recording_oracle = RECORDING_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow response = client.post( @@ -130,7 +130,7 @@ def test_incoming_webhook_401(client: TestClient) -> None: mock_get_web3.return_value = Web3(HTTPProvider(Networks.localhost)) mock_escrow = Mock() mock_escrow.launcher = escrow_address - mock_escrow.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow.recording_oracle = RECORDING_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow response = client.post( "/oracle-webhook", diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index 06a714b69f..53226b1ac4 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -93,7 +93,7 @@ def test_get_escrow_manifest_invalid_address(self): def test_get_escrow_manifest_invalid_url(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: - self.escrow_data.manifestUrl = "invalid_url" + self.escrow_data.manifest_url = "invalid_url" mock_function.return_value = self.escrow_data with self.assertRaises(StorageClientError) as error: get_escrow_manifest(chain_id, escrow_address) @@ -125,7 +125,7 @@ def test_get_job_launcher_address_empty_escrow(self): def test_get_recording_oracle_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_function.return_value = self.escrow_data recording_oracle_address = get_recording_oracle_address(chain_id, escrow_address) self.assertIsInstance(recording_oracle_address, str) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py index a655dc873b..b2b7279045 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py @@ -68,7 +68,7 @@ def test_get_recording_oracle_url(self): with patch("src.chain.kvstore.get_web3") as mock_function, patch( "src.chain.kvstore.get_escrow" ) as mock_escrow, patch("src.chain.kvstore.StakingClient.get_leader") as mock_leader: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data mock_leader.return_value = {"webhook_url": DEFAULT_URL} mock_function.return_value = self.w3 @@ -86,7 +86,7 @@ def test_get_recording_oracle_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_web3") as mock_function, patch( "src.chain.kvstore.get_escrow" ) as mock_escrow, patch("src.chain.kvstore.StakingClient.get_leader") as mock_leader: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data mock_leader.return_value = {"webhook_url": ""} mock_function.return_value = self.w3 diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py index f77cd30ded..ab0b1655db 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py @@ -267,7 +267,7 @@ def test_process_outgoing_recording_oracle_webhooks(self): w3.eth.chain_id = ChainId.LOCALHOST.value mock_web3.return_value = w3 mock_escrow_data = Mock() - mock_escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = mock_escrow_data mock_leader.return_value = {"webhook_url": DEFAULT_URL} mock_response = MagicMock() diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco.zip b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco.zip deleted file mode 100644 index 5b335a0873072e493b76f8a5c59ca28ad61efb9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1146 zcmb_cy>8nu5O(UcPvE%&hYkh7k`(p(3SPW*C<23)Xql-@8YGp}Fg)~K`ZitrKs{Qr zgG7Liz zn{hz4uDia8B&}uV6-I8HLtu^2;}vnQLM&*;3X-uz41oC);v&sRR$!hnz=$~%Mib16 zlH|qzK%79;ui;qagF3Ps+EUEo8Ej<)4C=8zYDlM~h0B#$)X)yZg%j(ej8G6(*=oVa!#d2{Db?sQhsu|7dWb76(&AWa6-n0hrVNtM-H~aBYAJ}j2W3zII z4OU>(!MZ-I4wZA%c)@~@2Z?PjS{w8}hvm_NRTp5H;{)M}Xt??kE_XobEZ%tGnjY+F zBEzbmp~ef=_E7t4%ejG9XRtDw#*jFJDK)r(R5Vxyi8G8llv6q$E*yb_xIZZ$SBH!(>#wbH(D#Ag=qOgy6zQL4)WDtCYwT1Lj=c+N=KY} vk;b2QliRCTH`kZ1ug$#{etvxq|9ZRi!ew@H`{r%vc?F*dlz-qFJR1E2ne$^D literal 0 HcmV?d00001 diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json new file mode 100644 index 0000000000..7773edb38d --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json @@ -0,0 +1 @@ +{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":""},{"id":2,"name":"dog","supercategory":""}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":34414.705200000004,"bbox":[337.96,209.86,272.01,126.52],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":40618.613400000024,"bbox":[856.68,261.52,214.03,189.78],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":47841.0129,"bbox":[247.29,404.9,159.21,300.49],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":4,"image_id":6,"category_id":2,"segmentation":[],"area":443166.7967999999,"bbox":[105.23,396.85,524.16,845.48],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":5,"image_id":6,"category_id":2,"segmentation":[],"area":427665.63399999996,"bbox":[592.07,142.07,407.32,1049.95],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good.zip b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good.zip new file mode 100644 index 0000000000000000000000000000000000000000..cfd3b0162b4ed24fe351b0f810470d60c1168ffa GIT binary patch literal 5859 zcmcJTQEuBf5Qg3DcAxj5*aPsb0Ktl+C{n&cUh=jJ6oFTzWzElzGn^Ug{_gFs???9M_rJ|w-@g9$^6`(+$i6;cE=tp^ zjLIZSi;wF#FS2y<9KWu!BrS`{ES^W3WywBcdP|ncG){}SAmdM4l18hTj0vG6nUS$a z$tGXg@$LaL={zIjEtzI%nJ15%GRyZLoke9lndULxaFe}x78lbzSzG=6L~VwWFIdcR z_BDZsaOjxSTJ^tb78o&>C$(3S)q=Cn zYl~1jt#Z*EgXes5+~R3A&6?5PInlb@h&fIU(~YU zscc{e=c@g|Vk3`RY))v6-S2Z*(Hkt{rB#-Pq6Js>rW^cBSAf3i)Q;^Nf1 zM*SF5ra##xO*z@IO*On-6*b|cU{{5>E`U|3nAC$q?K_N%c(IDpeVMoQ4pvZvk>eSH zLV?a+_BB2#_x0S?q%Fy&)8%GnYsEZT7BM9#vh%#^ z3#y4-HM4Ieeh?5aoo-f5vw@;^QgLp^3AN7@m0B`C9D>HN-vxsJxfw=E&4i#TFl-Rl zIQF|>P#zl?oH!@YKzd1_NsYkbR4K#Iw+s+yu0R(KYFvYZlM{3OiMY)h3Q-kESGlec7G&T(oV(%Ittpg?k>dTNBHAJ|%0$n(^VY-<|aySOb zO(Ncgz;Wz%(ja{`G&o9*cP`CsLCBP#zQ9uG(0#<*YM~-Gfz@3IcLEj z$LCC;lgQ}|aF`sT3)8jsG}GwXlxAEG5pb?RCy&z{*wb_kkPNE&qm6H?>JZ(5BhU%t zvRhIZV=a1X_U}cK3I0etG})=g*z12h#ZS f|G)OXlP|U1PoeMc-u-58c!S?R@%{ymu}7othUOe+ literal 0 HcmV?d00001 diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json new file mode 100644 index 0000000000..a45e8dfeb0 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json @@ -0,0 +1 @@ +{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":"","keypoints":["1"],"skeleton":[]},{"id":3,"name":"dog","supercategory":"","keypoints":["1"],"skeleton":[]},{"id":5,"name":"mouse","supercategory":"","keypoints":["1"],"skeleton":[]}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":0.0,"bbox":[500.95,287.88,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[500.95,287.88,2],"num_keypoints":1},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":0.0,"bbox":[974.5,373.49,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[974.5,373.49,2],"num_keypoints":1},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":0.0,"bbox":[337.22,568.8,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[337.22,568.8,2],"num_keypoints":1},{"id":4,"image_id":2,"category_id":3,"segmentation":[],"area":0.0,"bbox":[370.01,558.54,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[370.01,558.54,2],"num_keypoints":1},{"id":5,"image_id":3,"category_id":1,"segmentation":[],"area":0.0,"bbox":[408.27,331.58,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[408.27,331.58,2],"num_keypoints":1},{"id":6,"image_id":4,"category_id":3,"segmentation":[],"area":0.0,"bbox":[514.42,258.32,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[514.42,258.32,2],"num_keypoints":1},{"id":7,"image_id":4,"category_id":3,"segmentation":[],"area":0.0,"bbox":[485.23,695.62,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[485.23,695.62,2],"num_keypoints":1},{"id":8,"image_id":5,"category_id":3,"segmentation":[],"area":0.0,"bbox":[377.65,1230.59,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[377.65,1230.59,2],"num_keypoints":1},{"id":9,"image_id":6,"category_id":3,"segmentation":[],"area":0.0,"bbox":[776.26,439.85,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[776.26,439.85,2],"num_keypoints":1},{"id":10,"image_id":6,"category_id":3,"segmentation":[],"area":0.0,"bbox":[310.51,535.6,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[310.51,535.6,2],"num_keypoints":1},{"id":11,"image_id":7,"category_id":1,"segmentation":[],"area":0.0,"bbox":[469.52,401.96,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[469.52,401.96,2],"num_keypoints":1},{"id":12,"image_id":8,"category_id":1,"segmentation":[],"area":0.0,"bbox":[767.03,1311.69,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[767.03,1311.69,2],"num_keypoints":1},{"id":13,"image_id":9,"category_id":3,"segmentation":[],"area":0.0,"bbox":[707.75,257.13,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[707.75,257.13,2],"num_keypoints":1},{"id":14,"image_id":10,"category_id":3,"segmentation":[],"area":0.0,"bbox":[364.42,784.27,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[364.42,784.27,2],"num_keypoints":1},{"id":15,"image_id":11,"category_id":3,"segmentation":[],"area":0.0,"bbox":[896.64,491.84,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[896.64,491.84,2],"num_keypoints":1},{"id":16,"image_id":11,"category_id":3,"segmentation":[],"area":0.0,"bbox":[567.3,341.49,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[567.3,341.49,2],"num_keypoints":1},{"id":17,"image_id":12,"category_id":1,"segmentation":[],"area":0.0,"bbox":[774.69,418.53,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[774.69,418.53,2],"num_keypoints":1},{"id":18,"image_id":13,"category_id":1,"segmentation":[],"area":0.0,"bbox":[490.89,369.36,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[490.89,369.36,2],"num_keypoints":1}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt.zip b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt.zip new file mode 100644 index 0000000000000000000000000000000000000000..163e5bfcdd9713fd93f2d0e9790b6e34b6399540 GIT binary patch literal 6483 zcmcJUPm-fH6vjQ7Bqx|f*4ibdQjmm@@D+N~yQFHW%R}4P&9n_Jz?pQpyvRwiNDh!w z2 z$|OsRpSN*dWa;cRe%oeAS{AcKyo`41l6}VTo~)C3oEC9GrXTktjW#iv5<*F`AXAT$ zUB0%*hbN4r%ZyC-WS*sEo;>f$EI+<=5tZ?5p2v8?N%rJLT+H)iYxR!WiR9=TDhEGl12FfVxdDyUgBi+Qj)1rJ|)X!9nVftNj59a zK5s3;DQH!QmN;DEB0m&{p*ZlSM2} zvSCv;um^3`;b3tgFPGRfXpOzzZCNoGEb^sOmWQGPR}Q8dd`(w`SW=63I9%P3brTs; zZ>bIkYZ|J}*b1TXQcKn!>>49+x?|h5fz)8K9EsDd+m%znaJs0*ow{9HNDn7lmnklq z(KYJ-Ic0{Et;>{C9lKP+`&Hoy4a2UC_+)^URm@HYXYV^77x8Knr$?K&eur6L!l>~K zN%H)h{f@y<`%F=(CG$gSU%;z^Yw0FtzsFii@u&~ zf@WgvSDZ3>;UW4+m~uY$ONg z%IGNWGha}F=twa}bX7~A!!gQ7L0=93iRRFcB$q;VYnqcQfn3{%7yfp z8pTCh3cWE=5Ey*y5DfZBZvj}A2R zB;}HG7K~C4S_-{HPJ4jz?GRmQK-xpshF%oS8<%5boGZ}FVoF3s;AcajNsr30sA55M3`*7@6akT~U zc7uIojnnv?jauMyG}z@4or){_FQS lsv98q^YqinFc`?eV4vKSMzp7 z4`um>8d)%GDS_jNwt4vRUfpwL-QB+b@xzV(`S(xv@sH8u@l*QpTx5B<9#7L*vRRbuGlpNuBAcZ7I$e{|r&p3E%an`=p(LA|s+DtL37Y}}Q|FuIp#_1d9XZa`I8wz-3#vuLj2Z$b z6zIZ1id>DOqllp-3ul>gL*Rr0T{y7Ft8rqhn5NQ7rs5%Jg892(;Br^PNNJfssu{-y z2~9A67Yy9|Gz>^jl>rwk!;F-1{j;wLPAVojHIZc6QH~?0s4-0HJ`013BZe-ZRuMEd zG2@yV?Yy|4yH#K~mb7I9y4(1n9W=zxPN65yN}9nBR}f(oA-VhEg2pbN)0 zSXc8XrrdWeh*1v+_xPNKJ!mL&5(WJM>?j~Q zGv9243nX;nxK847h=dCTI(dQy!TZrAK+;r}YMt_p!T=pYD9{NMvHMc~a2`^*|=aq)**pg210Va}jH zpc5$Q5O6++33n!t!UYU0Rid=YVTyJj& dict: escrow = get_escrow(chain_id, escrow_address) - manifest_content = StorageClient.download_file_from_url(escrow.manifestUrl) + manifest_content = StorageClient.download_file_from_url(escrow.manifest_url) return json.loads(manifest_content.decode("utf-8")) diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 3353b5e1b3..932596795c 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -76,7 +76,7 @@ class StorageConfig: region = os.environ.get("STORAGE_REGION", "") access_key = os.environ.get("STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") + data_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) @classmethod @@ -90,9 +90,9 @@ def bucket_url(cls): scheme = "https://" if cls.secure else "http://" if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" class ExchangeOracleStorageConfig: @@ -100,7 +100,7 @@ class ExchangeOracleStorageConfig: region = os.environ.get("EXCHANGE_ORACLE_STORAGE_REGION", "") access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") + data_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") secure = str_to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) @classmethod @@ -114,9 +114,9 @@ def bucket_url(cls): scheme = "https://" if cls.secure else "http://" if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py index 710001cc8c..1326152839 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py @@ -94,7 +94,7 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge ) excor_bucket_host = Config.exchange_oracle_storage_config.provider_endpoint_url() - excor_bucket_name = Config.exchange_oracle_storage_config.results_bucket_name + excor_bucket_name = Config.exchange_oracle_storage_config.data_bucket_name excor_annotation_meta_path = compose_bucket_filename( webhook.escrow_address, @@ -175,12 +175,12 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge # TODO: add encryption storage_client.create_file( - Config.storage_config.results_bucket_name, + Config.storage_config.data_bucket_name, recor_merged_annotations_path, validation_results.resulting_annotations, ) storage_client.create_file( - Config.storage_config.results_bucket_name, + Config.storage_config.data_bucket_name, recor_validation_meta_path, validation_metafile, ) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index c4774811df..f6051c833c 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -51,7 +51,7 @@ def escrow(self, status: str = "Pending", balance: float = amount): mock_escrow.status = status mock_escrow.balance = balance mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS - mock_escrow.manifestUrl = DEFAULT_URL + mock_escrow.manifest_url = DEFAULT_URL return mock_escrow def test_validate_escrow(self): From 2b3e8c5db1791d7e84cfeb1510803605883bed79 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 26 Jan 2024 14:48:17 +0200 Subject: [PATCH 03/82] Refactor cloud storage api --- .../cvat/exchange-oracle/src/chain/escrow.py | 2 +- .../cvat/exchange-oracle/src/core/types.py | 5 - .../src/crons/state_trackers.py | 2 +- .../cvat/exchange-oracle/src/cvat/tasks.py | 88 ++++++++---------- .../src/services/cloud/__init__.py | 16 +--- .../src/services/cloud/client.py | 66 +++---------- .../exchange-oracle/src/services/cloud/s3.py | 88 ++++++++++++++++++ .../src/services/cloud/types.py | 44 +++++++++ .../cloud/utils.py} | 93 ++++++++++--------- 9 files changed, 238 insertions(+), 166 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/types.py rename packages/examples/cvat/exchange-oracle/src/{utils/cloud_storage.py => services/cloud/utils.py} (53%) diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 40663be6e5..b9593d8b73 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -6,7 +6,7 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient -from src.utils.cloud_storage import parse_bucket_url +from src.services.cloud.utils import parse_bucket_url def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: diff --git a/packages/examples/cvat/exchange-oracle/src/core/types.py b/packages/examples/cvat/exchange-oracle/src/core/types.py index c68a3d7684..116e0dedf6 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/types.py +++ b/packages/examples/cvat/exchange-oracle/src/core/types.py @@ -49,11 +49,6 @@ class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): rectangle = "rectangle" -class CloudProviders(str, Enum, metaclass=BetterEnumMeta): - aws = "AWS_S3_BUCKET" - gcs = "GOOGLE_CLOUD_STORAGE" - - class OracleWebhookTypes(str, Enum, metaclass=BetterEnumMeta): exchange_oracle = "exchange_oracle" job_launcher = "job_launcher" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 90f4509981..0eaae2eca1 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -2,7 +2,7 @@ import src.cvat.api_calls as cvat_api import src.models.cvat as cvat_models -import src.services.cloud.client as cloud_client +import src.services.cloud.utils as cloud_client import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service from src.chain.escrow import get_escrow_manifest, validate_escrow diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py b/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py index a9df84d344..fbbad78a0f 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py @@ -25,12 +25,12 @@ from src.chain.escrow import get_escrow_manifest from src.core.config import Config from src.core.manifest import TaskManifest -from src.core.types import CloudProviders, CvatLabelType, TaskStatus, TaskType +from src.core.types import CvatLabelType, TaskStatus, TaskType from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME -from src.services.cloud.client import S3Client +from src.services.cloud import CloudProviders, StorageClient, s3 +from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url from src.utils.assignments import parse_manifest -from src.utils.cloud_storage import BucketConfig, compose_bucket_url, parse_bucket_url from src.utils.logging import NullLogger, get_function_logger LABEL_TYPE_MAPPING = { @@ -148,7 +148,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.max_embedded_point_radius_percent = 0.01 self.embedded_point_color = (0, 255, 255) - self.oracle_data_bucket = BucketConfig.from_url(Config.storage_config.bucket_url()) + self.oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) # TODO: add # credentials=BucketCredentials() "Exchange Oracle's private bucket info" @@ -173,29 +173,26 @@ def set_logger(self, logger: Logger): return self def _download_input_data(self): - data_bucket = BucketConfig.from_url(self.manifest.data.data_url) - gt_bucket = BucketConfig.from_url(self.manifest.validation.gt_url) - points_bucket = BucketConfig.from_url(self.manifest.data.points_url) + data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + points_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.points_url) data_storage_client = self._make_cloud_storage_client(data_bucket) gt_storage_client = self._make_cloud_storage_client(gt_bucket) points_storage_client = self._make_cloud_storage_client(points_bucket) - data_filenames = [ - file_info.key - for file_info in data_storage_client.list_files( - data_bucket.url.bucket_name, - data_bucket.url.path, - ) - ] + data_filenames = data_storage_client.list_filenames( + data_bucket.url.bucket_name, + prefix=data_bucket.url.path, + ) self.input_filenames = filter_image_files(data_filenames) - self.input_gt_data = gt_storage_client.download_fileobj( + self.input_gt_data = gt_storage_client.download_file( gt_bucket.url.bucket_name, gt_bucket.url.path, ) - self.input_points_data = points_storage_client.download_fileobj( + self.input_points_data = points_storage_client.download_file( points_bucket.url.bucket_name, points_bucket.url.path, ) @@ -747,11 +744,11 @@ def _upload_config(self): roi_filenames_file = dump_json({str(k): v for k, v in self.roi_filenames.items()}) file_list.append((roi_filenames_file, layout.ROI_FILENAMES_FILENAME)) - client = self._make_cloud_storage_client(self.oracle_data_bucket) + storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) bucket_name = self.oracle_data_bucket.url.bucket_name prefix = self.oracle_data_bucket_prefix for file_data, filename in file_list: - client.create_file(bucket_name, os.path.join(prefix, filename), file_data) + storage_client.create_file(bucket_name, os.path.join(prefix, filename), file_data) def _draw_roi_point(self, roi_pixels: np.ndarray, roi_info: RoiInfo) -> np.ndarray: center = (roi_info.point_x, roi_info.point_y) @@ -791,7 +788,7 @@ def _extract_and_upload_rois(self): assert self.input_filenames is not self._not_configured assert self.roi_filenames is not self._not_configured - src_bucket = BucketConfig.from_url(self.manifest.data.data_url) + src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) src_prefix = "" dst_bucket = self.oracle_data_bucket dst_prefix = self.oracle_data_bucket_prefix @@ -816,7 +813,7 @@ def _extract_and_upload_rois(self): if not image_roi_infos: continue - image_bytes = src_client.download_fileobj( + image_bytes = src_client.download_file( src_bucket.url.bucket_name, os.path.join(src_prefix, filename) ) image_pixels = decode_image(image_bytes) @@ -855,7 +852,7 @@ def _create_on_cvat(self): assert self.job_layout is not self._not_configured assert self.label_configuration is not self._not_configured - input_data_bucket = BucketConfig.from_url(self.manifest.data.data_url) + input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) oracle_bucket = self.oracle_data_bucket oracle_bucket_prefix = self.oracle_data_bucket_prefix @@ -919,19 +916,9 @@ def _create_on_cvat(self): with SessionLocal.begin() as session: db_service.create_data_upload(session, cvat_task_id=task.id) - def _make_cloud_storage_client(self, bucket_info: BucketConfig) -> S3Client: - match bucket_info.provider: - case CloudProviders.aws: - client_kwargs = {} - if bucket_info.credentials: - client_kwargs["access_key"] = bucket_info.credentials.access_key - client_kwargs["secret_key"] = bucket_info.credentials.secret_key - - client = S3Client(bucket_info.url.host_url, **client_kwargs) - case _: - raise Exception("Unsupported cloud provider") - - return client + @classmethod + def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: + return cloud_service.make_client(bucket_info) def build(self): self._download_input_data() @@ -1043,21 +1030,22 @@ def create_task(escrow_address: str, chain_id: int) -> None: TaskType.image_points, TaskType.image_label_binary, ]: - data_bucket_address = parse_bucket_url(manifest.data.data_url) - gt_bucket_address = parse_bucket_url(manifest.validation.gt_url) + data_bucket = parse_bucket_url(manifest.data.data_url) + gt_bucket = parse_bucket_url(manifest.validation.gt_url) + + data_bucket_client = cloud_service.make_client(data_bucket) + gt_bucket_client = cloud_service.make_client(gt_bucket) # Task configuration creation - data_filenames = cloud_service.list_files( - data_bucket_address.host_url, - data_bucket_address.bucket_name, - data_bucket_address.path, + data_filenames = data_bucket_client.list_filenames( + data_bucket.bucket_name, + prefix=data_bucket.path, ) data_filenames = filter_image_files(data_filenames) - gt_file_data = cloud_service.download_file( - gt_bucket_address.host_url, - gt_bucket_address.bucket_name, - gt_bucket_address.path, + gt_file_data = gt_bucket_client.download_file( + gt_bucket.bucket_name, + gt_bucket.path, ) # Validate and parse GT @@ -1068,9 +1056,9 @@ def create_task(escrow_address: str, chain_id: int) -> None: # Register cloud storage on CVAT to pass user dataset cloud_storage = cvat_api.create_cloudstorage( - CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[data_bucket_address.provider], - data_bucket_address.host_url, - data_bucket_address.bucket_name, + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[data_bucket.provider], + data_bucket.host_url, + data_bucket.bucket_name, ) # Create a project @@ -1092,9 +1080,9 @@ def create_task(escrow_address: str, chain_id: int) -> None: escrow_address, chain_id, compose_bucket_url( - data_bucket_address.bucket_name, - bucket_host=data_bucket_address.host_url, - provider=data_bucket_address.provider, + data_bucket.bucket_name, + bucket_host=data_bucket.host_url, + provider=data_bucket.provider, ), cvat_webhook_id=webhook.id, ) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py index eb128cc100..007394d9ed 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py @@ -1,13 +1,3 @@ -from typing import Optional - -from src.services.cloud.client import S3Client - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_fileobj(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, path: Optional[str] = None) -> list[str]: - client = S3Client(bucket_host) - return [f.key for f in client.list_files(bucket_name, path=path)] +from src.services.cloud.client import StorageClient +from src.services.cloud.types import CloudProviders +from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index 3d819d8512..0a80d1706e 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -1,62 +1,24 @@ -# Copyright (C) 2022 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - -from io import BytesIO +from abc import ABCMeta, abstractmethod from typing import List, Optional -from urllib.parse import unquote - -import boto3 -from botocore.exceptions import ClientError -from botocore.handlers import disable_signing - - -class S3Client: - def __init__( - self, - endpoint_url: str, - *, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - ) -> None: - s3 = boto3.resource( - "s3", - **(dict(aws_access_key_id=access_key) if access_key else {}), - **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=unquote(endpoint_url), - ) - - self.resource = s3 - self.client = s3.meta.client - if not access_key and not secret_key: - self.client.meta.events.register("choose-signer.s3.*", disable_signing) +class StorageClient(metaclass=ABCMeta): + @abstractmethod def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + ... + @abstractmethod def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + ... + @abstractmethod def file_exists(self, bucket: str, filename: str) -> bool: - try: - self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - else: - raise + ... - def download_fileobj(self, bucket: str, key: str) -> bytes: - with BytesIO() as data: - self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) - return data.getvalue() + @abstractmethod + def download_file(self, bucket: str, key: str) -> bytes: + ... - def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(unquote(bucket)).objects - if path: - objects = objects.filter(Prefix=unquote(path).strip("/\\") + "/") - else: - objects = objects.all() - return list(objects) + @abstractmethod + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + ... diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py new file mode 100644 index 0000000000..ed00f2799e --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from dataclasses import dataclass +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +import boto3 +from botocore.exceptions import ClientError +from botocore.handlers import disable_signing + +from src.services.cloud.client import StorageClient +from src.services.cloud.types import BucketCredentials + + +class S3Client(StorageClient): + def __init__( + self, + endpoint_url: str, + *, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + ) -> None: + s3 = boto3.resource( + "s3", + **(dict(aws_access_key_id=access_key) if access_key else {}), + **(dict(aws_secret_access_key=secret_key) if secret_key else {}), + endpoint_url=unquote(endpoint_url), + ) + + self.resource = s3 + self.client = s3.meta.client + + if not access_key and not secret_key: + self.client.meta.events.register("choose-signer.s3.*", disable_signing) + + def create_file(self, bucket: str, filename: str, data: bytes = b""): + self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + + def remove_file(self, bucket: str, filename: str): + self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + + def file_exists(self, bucket: str, filename: str) -> bool: + try: + self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return False + else: + raise + + def download_file(self, bucket: str, key: str) -> bytes: + with BytesIO() as data: + self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) + return data.getvalue() + + def list_files(self, bucket: str, *, prefix: Optional[str] = None) -> List: + objects = self.resource.Bucket(unquote(bucket)).objects + if prefix: + objects = objects.filter(Prefix=unquote(prefix).strip("/\\") + "/") + else: + objects = objects.all() + return list(objects) + + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + return [file_info.key for file_info in self.list_files(bucket=bucket, prefix=prefix)] + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +DEFAULT_S3_HOST = "s3.amazonaws.com" + + +def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: + client = S3Client(bucket_host) + return client.download_file(bucket_name, filename) + + +def list_files(bucket_host: str, bucket_name: str, *, prefix: Optional[str] = None) -> List[str]: + client = S3Client(bucket_host) + return client.list_filenames(bucket_name, prefix=prefix) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py new file mode 100644 index 0000000000..ee89883d52 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum, auto +from typing import Optional + +from src.utils.enums import BetterEnumMeta + + +class CloudProviders(Enum, metaclass=BetterEnumMeta): + aws = auto() + gcs = auto() + + +@dataclass +class BucketUrl: + provider: CloudProviders + host_url: str + bucket_name: str + path: str + + +class BucketCredentials: + pass + + +@dataclass +class BucketAccessInfo: + url: BucketUrl + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_raw_url(cls, url: str) -> BucketAccessInfo: + from src.services.cloud.utils import parse_bucket_url + + return cls.from_parsed_url(parse_bucket_url(url)) + + @classmethod + def from_parsed_url(cls, parsed_url: BucketUrl) -> BucketAccessInfo: + return BucketAccessInfo(url=parsed_url) + + @property + def provider(self) -> CloudProviders: + return self.url.provider diff --git a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py similarity index 53% rename from packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py rename to packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py index 0e0d968887..33b9a4f7ee 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py @@ -1,56 +1,19 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Optional +from typing import Optional, Union, overload from urllib.parse import urlparse from src.core.config import Config -from src.core.types import CloudProviders +from src.services.cloud.client import StorageClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, BucketUrl, CloudProviders from src.utils.net import is_ipv4 -@dataclass -class ParsedBucketUrl: - provider: CloudProviders - host_url: str - bucket_name: str - path: str - - -class BucketCredentials: - pass - - -@dataclass -class S3BucketCredentials(BucketCredentials): - access_key: str - secret_key: str - - -@dataclass -class BucketConfig: - provider: CloudProviders - url: ParsedBucketUrl - credentials: Optional[BucketCredentials] = None - - @classmethod - def from_url(cls, url: str) -> BucketConfig: - return cls.from_parsed_url(parse_bucket_url(url)) - - @classmethod - def from_parsed_url(cls, parsed_url: ParsedBucketUrl) -> BucketConfig: - return BucketConfig(parsed_url.provider, url=parsed_url) - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def parse_bucket_url(data_url: str) -> ParsedBucketUrl: +def parse_bucket_url(data_url: str) -> BucketUrl: parsed_url = urlparse(data_url) if parsed_url.netloc.endswith(DEFAULT_S3_HOST): # AWS S3 bucket - return ParsedBucketUrl( + return BucketUrl( provider=CloudProviders.aws, host_url=f"https://{DEFAULT_S3_HOST}", bucket_name=parsed_url.netloc.split(".")[0], @@ -71,7 +34,7 @@ def parse_bucket_url(data_url: str) -> ParsedBucketUrl: bucket_name = parsed_url.netloc.split(".")[0] path = parsed_url.path.lstrip("/") - return ParsedBucketUrl( + return BucketUrl( provider=CloudProviders.aws, host_url=f"{parsed_url.scheme}://{host}", bucket_name=bucket_name, @@ -89,3 +52,45 @@ def compose_bucket_url( return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" case CloudProviders.gcs: return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" + + +@overload +def make_client(url: BucketUrl, credentials: Optional[BucketCredentials] = None) -> StorageClient: + ... + + +@overload +def make_client( + bucket_info: BucketAccessInfo, +) -> StorageClient: + ... + + +def make_client( + _pos1: Union[BucketUrl, BucketAccessInfo, None] = None, + *, + bucket_info: Optional[BucketAccessInfo] = None, + url: Optional[BucketUrl] = None, + credentials: Optional[BucketCredentials] = None, +) -> StorageClient: + if _pos1 is not None: + if isinstance(_pos1, BucketAccessInfo): + bucket_info = _pos1 + else: + url = _pos1 + + if bucket_info is None: + bucket_info = BucketAccessInfo(url=url, credentials=credentials) + + match bucket_info.provider: + case CloudProviders.aws: + client_kwargs = {} + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + client = S3Client(bucket_info.url.host_url, **client_kwargs) + case _: + raise Exception("Unsupported cloud provider") + + return client From 8b486207c4a8b3e244598d75c72ca8123f619de0 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 26 Jan 2024 22:48:30 +0200 Subject: [PATCH 04/82] Implement job downloading --- .../cvat/exchange-oracle/src/core/config.py | 1 + .../crons/process_job_launcher_webhooks.py | 2 +- .../src/crons/state_trackers.py | 14 +- .../src/handlers/annotation.py | 217 --------- .../tasks.py => handlers/job_creation.py} | 201 +++++--- .../src/handlers/job_export.py | 447 ++++++++++++++++++ .../exchange-oracle/src/utils/assignments.py | 8 +- 7 files changed, 601 insertions(+), 289 deletions(-) delete mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/annotation.py rename packages/examples/cvat/exchange-oracle/src/{cvat/tasks.py => handlers/job_creation.py} (88%) create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/job_export.py diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index b37a7a622d..e0b600a2da 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -99,6 +99,7 @@ class StorageConfig: access_key = os.environ.get("STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("STORAGE_SECRET_KEY", "") data_bucket_name = os.environ.get("STORAGE_DATA_BUCKET_NAME", "") + results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) @classmethod diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py index c90e789d27..ecde495883 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py @@ -3,7 +3,7 @@ import httpx from human_protocol_sdk.constants import Status as EscrowStatus -import src.cvat.tasks as cvat +import src.handlers.job_creation as cvat import src.services.cvat as cvat_db_service import src.services.webhook as oracle_db_service from src.chain.escrow import validate_escrow diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 0eaae2eca1..71492dd0fc 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -15,14 +15,14 @@ from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatus from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.annotation import ( +from src.handlers.job_export import ( CVAT_EXPORT_FORMAT_MAPPING, FileDescriptor, postprocess_annotations, prepare_annotation_metafile, ) from src.log import ROOT_LOGGER_NAME -from src.utils.assignments import compose_output_annotation_filename, parse_manifest +from src.utils.assignments import compose_results_bucket_filename, parse_manifest from src.utils.logging import get_function_logger module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" @@ -266,8 +266,10 @@ def retrieve_annotations() -> None: ) annotation_files.extend(job_annotations.values()) postprocess_annotations( - annotation_files, - project_annotations_file_desc, + escrow_address=project.escrow_address, + chain_id=project.chain_id, + annotations=annotation_files, + merged_annotation=project_annotations_file_desc, manifest=manifest, project_images=cvat_service.get_project_images(session, project.cvat_id), ) @@ -283,7 +285,7 @@ def retrieve_annotations() -> None: f.key for f in storage_client.list_files( StorageConfig.data_bucket_name, - path=compose_output_annotation_filename( + prefix=compose_results_bucket_filename( project.escrow_address, project.chain_id, "", @@ -296,7 +298,7 @@ def retrieve_annotations() -> None: storage_client.create_file( StorageConfig.data_bucket_name, - compose_output_annotation_filename( + compose_results_bucket_filename( project.escrow_address, project.chain_id, file_descriptor.filename, diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py b/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py deleted file mode 100644 index 3913c98ad6..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py +++ /dev/null @@ -1,217 +0,0 @@ -import io -import os -import zipfile -from glob import glob -from tempfile import TemporaryDirectory -from typing import Dict, List, Sequence - -import datumaro as dm -from attrs import define -from defusedxml import ElementTree as ET - -from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta -from src.core.manifest import TaskManifest -from src.core.types import TaskType -from src.cvat.tasks import DM_DATASET_FORMAT_MAPPING -from src.models.cvat import Image, Job -from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive - -CVAT_EXPORT_FORMAT_MAPPING = { - TaskType.image_label_binary: "CVAT for images 1.1", - TaskType.image_points: "CVAT for images 1.1", - TaskType.image_boxes: "COCO 1.0", -} - -CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { - "CVAT for images 1.1": "cvat", - "COCO 1.0": "coco_instances", -} - - -@define -class FileDescriptor: - filename: str - file: io.RawIOBase - - -def prepare_annotation_metafile( - jobs: List[Job], job_annotations: Dict[int, FileDescriptor] -) -> FileDescriptor: - """ - Prepares a task/project annotation descriptor file with annotator mapping. - """ - - meta = AnnotationMeta( - jobs=[ - JobMeta( - job_id=job.cvat_id, - annotation_filename=job_annotations[job.cvat_id].filename, - annotator_wallet_address=job.latest_assignment.user_wallet_address, - assignment_id=job.latest_assignment.id, - ) - for job in jobs - ] - ) - - return FileDescriptor(ANNOTATION_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) - - -def flatten_points(input_points: List[dm.Points]) -> List[dm.Points]: - results = [] - - for pts in input_points: - for point_idx in range(len(pts.points) // 2): - point_x = pts.points[2 * point_idx + 0] - point_y = pts.points[2 * point_idx + 1] - results.append(dm.Points([point_x, point_y], label=pts.label)) - - return results - - -def fix_cvat_annotations(dataset_root: str): - for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): - with open(annotation_filename, "rb+") as f: - doc = ET.parse(f) - doc_root = doc.getroot() - - if doc_root.find("meta/project"): - # put labels into each task, if needed - # datumaro doesn't support /meta/project/ tag, but works with tasks, - # which is nested in the meta/project/ - labels_element = doc_root.find("meta/project/labels") - if not labels_element: - continue - - for task_element in doc_root.iterfind("meta/project/tasks/task"): - task_element.append(labels_element) - elif job_meta := doc_root.find("meta/job"): - # just rename the job into task for the same reasons - job_meta.tag = "task" - else: - continue - - f.seek(0) - f.truncate() - doc.write(f, encoding="utf-8") - - -def convert_point_arrays_dataset_to_1_point_skeletons( - dataset: dm.Dataset, labels: List[str] -) -> dm.Dataset: - def _get_skeleton_label(original_label: str) -> str: - return original_label + "_sk" - - new_label_cat = dm.LabelCategories.from_iterable( - [_get_skeleton_label(label) for label in labels] - + [(label, _get_skeleton_label(label)) for label in labels] - ) - new_points_cat = dm.PointsCategories.from_iterable( - (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels - ) - converted_dataset = dm.Dataset( - categories={ - dm.AnnotationType.label: new_label_cat, - dm.AnnotationType.points: new_points_cat, - }, - media_type=dm.Image, - ) - - label_id_map: Dict[int, int] = { - original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] - for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) - } # old id -> new id - - for sample in dataset: - points = [a for a in sample.annotations if isinstance(a, dm.Points)] - points = flatten_points(points) - - skeletons = [ - dm.Skeleton( - [p.wrap(label=label_id_map[p.label])], - label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], - ) - for p in points - ] - - converted_dataset.put(sample.wrap(annotations=skeletons)) - - return converted_dataset - - -def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]): - """ - Removes unknown images from the dataset inplace. - - On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, - with a suffix. We don't need these frames in the resulting dataset, - and we can safely remove them. - """ - if not isinstance(known_frames, set): - known_frames = set(known_frames) - - for sample in list(dataset): - item_image_filename = sample.media.path - - if item_image_filename not in known_frames: - dataset.remove(sample.id, sample.subset) - - -def postprocess_annotations( - annotations: List[FileDescriptor], - merged_annotation: FileDescriptor, - *, - manifest: TaskManifest, - project_images: List[Image], -) -> None: - """ - Processes annotations and updates the files list inplace - """ - - task_type = manifest.annotation.type - - if task_type != TaskType.image_points: - return # CVAT export is fine - - # We need to convert point arrays, which cannot be represented in COCO directly, - # into the 1-point skeletons, compatible with COCO person keypoints, which is the - # required output format - input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] - resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] - - with TemporaryDirectory() as tempdir: - for ann_descriptor in annotations: - if not zipfile.is_zipfile(ann_descriptor.file): - raise ValueError("Annotation files must be zip files") - ann_descriptor.file.seek(0) - - extract_dir = os.path.join( - tempdir, - os.path.splitext(os.path.basename(ann_descriptor.filename))[0], - ) - extract_zip_archive(ann_descriptor.file, extract_dir) - - fix_cvat_annotations(extract_dir) - dataset = dm.Dataset.import_from(extract_dir, input_format) - - converted_dataset = convert_point_arrays_dataset_to_1_point_skeletons( - dataset, - labels=[label.name for label in manifest.annotation.labels], - ) - - if ann_descriptor.filename == merged_annotation.filename: - remove_duplicated_gt_frames( - converted_dataset, - known_frames=[image.filename for image in project_images], - ) - - export_dir = os.path.join( - tempdir, - os.path.splitext(os.path.basename(ann_descriptor.filename))[0] + "_conv", - ) - converted_dataset.export(export_dir, resulting_format, save_images=False) - - converted_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(export_dir, converted_dataset_archive) - converted_dataset_archive.seek(0) - - ann_descriptor.file = converted_dataset_archive diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py similarity index 88% rename from packages/examples/cvat/exchange-oracle/src/cvat/tasks.py rename to packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index fbbad78a0f..2d8a145336 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -1,5 +1,6 @@ from __future__ import annotations +import io import os import random import uuid @@ -16,7 +17,7 @@ import datumaro as dm import numpy as np from attrs import frozen -from datumaro.util import dump_json, take_by +from datumaro.util import dump_json, parse_json, take_by from datumaro.util.image import IMAGE_EXTENSIONS, decode_image, encode_image import src.cvat.api_calls as cvat_api @@ -28,9 +29,9 @@ from src.core.types import CvatLabelType, TaskStatus, TaskType from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME -from src.services.cloud import CloudProviders, StorageClient, s3 +from src.services.cloud import CloudProviders, StorageClient from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url -from src.utils.assignments import parse_manifest +from src.utils.assignments import compose_data_bucket_filename, parse_manifest from src.utils.logging import NullLogger, get_function_logger LABEL_TYPE_MAPPING = { @@ -88,6 +89,26 @@ class InvalidCategories(DatasetValidationError): class InvalidImageInfo(DatasetValidationError): pass + BboxPointMapping = Dict[int, int] + + @frozen + class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + RoiInfos = Sequence[RoiInfo] + + RoiFilenames = Dict[int, str] + max_discarded_threshold = 0.5 "The maximum allowed percent of discarded " "GT boxes, points, or samples for successful job launch" @@ -111,7 +132,9 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): # Output values self.gt_dataset: Union[dm.Dataset, self._NotConfigured] = self._not_configured - self.bbox_point_mapping: Union[Dict[int, int], self._NotConfigured] = self._not_configured + self.bbox_point_mapping: Union[ + self.BboxPointMapping, self._NotConfigured + ] = self._not_configured "bbox_id -> point_id" self.roi_size_estimations: Union[ @@ -119,8 +142,8 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): ] = self._not_configured "label_id -> (rel. w, rel. h)" - self.rois: Union[Sequence[self.RoiInfo], self._NotConfigured] = self._not_configured - self.roi_filenames: Union[Dict[int, str], self._NotConfigured] = self._not_configured + self.rois: Union[self.RoiInfos, self._NotConfigured] = self._not_configured + self.roi_filenames: Union[self.RoiFilenames, self._NotConfigured] = self._not_configured self.job_layout: Union[Sequence[Sequence[str]], self._NotConfigured] = self._not_configured "File lists per CVAT job" @@ -153,9 +176,6 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): # credentials=BucketCredentials() "Exchange Oracle's private bucket info" - self.oracle_data_bucket_prefix = f"{self.escrow_address}@{self.chain_id}" - "Filename prefix" - self.min_class_samples_for_roi_estimation = 100 def __enter__(self): @@ -559,7 +579,6 @@ def _estimate_roi_sizes(self): ] bbox_sizes_per_label = {} - gt_counts_per_label = {} for sample in self.gt_dataset: image_h, image_w = self.input_points_dataset.get(sample.id, sample.subset).image.size @@ -572,8 +591,6 @@ def _estimate_roi_sizes(self): ) ) - gt_counts_per_label[gt_bbox.label] = gt_counts_per_label.get(gt_bbox.label, 0) + 1 - # Consider bbox sides as normally-distributed random variables, estimate sigmas # For big enough datasets, it should be reasonable approximation # (due to the central limit theorem). This can work bad for small datasets, @@ -581,9 +598,9 @@ def _estimate_roi_sizes(self): classes_with_default_roi = [] roi_size_estimations_per_label = {} # label id -> (w, h) for label_id, label_sizes in bbox_sizes_per_label.items(): - if gt_counts_per_label[label_id] < self.min_class_samples_for_roi_estimation: + if len(label_sizes) < self.min_class_samples_for_roi_estimation: classes_with_default_roi.append(label_id) - estimated_size = (1, 1) + estimated_size = (2, 2) # 2 will yield just the image size after halving else: mean_size = np.average(label_sizes, axis=0) sigma = np.sqrt(np.var(label_sizes, axis=0)) @@ -604,20 +621,6 @@ def _estimate_roi_sizes(self): self.roi_size_estimations = roi_size_estimations_per_label - @frozen - class RoiInfo: - point_id: int - image_id: int - point_x: int - point_y: int - roi_x: int - roi_y: int - roi_w: int - roi_h: int - - def asdict(self) -> dict: - return attrs.asdict(self, recurse=False) - def _prepare_roi_info(self): assert self.gt_dataset is not self._not_configured assert self.roi_size_estimations is not self._not_configured @@ -654,7 +657,7 @@ def _prepare_roi_info(self): rois.append( self.RoiInfo( point_id=skeleton.id, - image_id=sample.attributes["id"], + original_image_key=sample.attributes["id"], point_x=new_point_x, point_y=new_point_y, roi_x=roi_left, @@ -706,49 +709,114 @@ def _prepare_job_layout(self): def _prepare_label_configuration(self): self.label_configuration = make_label_configuration(self.manifest) - class TaskParamsLayout: + class TaskMetaLayout: GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" ROI_INFO_FILENAME = "rois.json" ROI_FILENAMES_FILENAME = "roi_filenames.json" # this is separated from the general roi info to make name mangling more "optional" - def _upload_config(self): + class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return ( + Path(gt_dataset_dir) / "annotations" / "instances_default.json" + ).read_bytes() + + def serialize_bbox_point_mapping( + self, bbox_point_mapping: BoxesFromPointsTaskBuilder.BboxPointMapping + ) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: BoxesFromPointsTaskBuilder.RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames( + self, roi_filenames: BoxesFromPointsTaskBuilder.RoiFilenames + ) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.GT_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping( + self, bbox_point_mapping_data: bytes + ) -> BoxesFromPointsTaskBuilder.BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> BoxesFromPointsTaskBuilder.RoiInfos: + return [ + BoxesFromPointsTaskBuilder.RoiInfo(**roi_info) + for roi_info in parse_json(rois_info_data) + ] + + def parse_roi_filenames( + self, roi_filenames_data: bytes + ) -> BoxesFromPointsTaskBuilder.RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} + + def _upload_meta(self): # TODO: maybe extract into a separate function / class / library, # extract constants, serialization methods return TaskConfig from build() - layout = self.TaskParamsLayout + layout = self.TaskMetaLayout() + serializer = self.TaskMetaSerializer() file_list = [] - with TemporaryDirectory() as temp_dir: - gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") - self.gt_dataset.export(gt_dataset_dir, self.points_format) - file_list.append( - ( - ( - Path(gt_dataset_dir) / "annotations" / "person_keypoints_default.json" - ).read_bytes(), - layout.GT_FILENAME, - ) + file_list.append((self.input_points_data, layout.POINTS_FILENAME)) + file_list.append( + ( + serializer.serialize_gt_annotations(self.gt_dataset), + layout.GT_FILENAME, ) - - bbox_point_mapping_file = dump_json( - {str(k): str(v) for k, v in self.bbox_point_mapping.items()} ) - file_list.append((bbox_point_mapping_file, layout.BBOX_POINT_MAPPING_FILENAME)) - - rois_file = dump_json([roi_info.asdict() for roi_info in self.rois]) - file_list.append((rois_file, layout.ROI_INFO_FILENAME)) - - roi_filenames_file = dump_json({str(k): v for k, v in self.roi_filenames.items()}) - file_list.append((roi_filenames_file, layout.ROI_FILENAMES_FILENAME)) + file_list.append( + ( + serializer.serialize_bbox_point_mapping(self.bbox_point_mapping), + layout.BBOX_POINT_MAPPING_FILENAME, + ) + ) + file_list.append((serializer.serialize_roi_info(self.rois), layout.ROI_INFO_FILENAME)) + file_list.append( + (serializer.serialize_roi_filenames(self.roi_filenames), layout.ROI_FILENAMES_FILENAME) + ) storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) bucket_name = self.oracle_data_bucket.url.bucket_name - prefix = self.oracle_data_bucket_prefix for file_data, filename in file_list: - storage_client.create_file(bucket_name, os.path.join(prefix, filename), file_data) + storage_client.create_file( + bucket_name, + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + file_data, + ) def _draw_roi_point(self, roi_pixels: np.ndarray, roi_info: RoiInfo) -> np.ndarray: center = (roi_info.point_x, roi_info.point_y) @@ -791,7 +859,6 @@ def _extract_and_upload_rois(self): src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) src_prefix = "" dst_bucket = self.oracle_data_bucket - dst_prefix = self.oracle_data_bucket_prefix src_client = self._make_cloud_storage_client(src_bucket) dst_client = self._make_cloud_storage_client(dst_bucket) @@ -802,7 +869,7 @@ def _extract_and_upload_rois(self): filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} - _roi_key = lambda e: e.image_id + _roi_key = lambda e: e.original_image_key rois_by_image: Dict[str, self.RoiInfo] = { image_id_to_filename[image_id]: list(g) for image_id, g in groupby(sorted(self.rois, key=_roi_key), key=_roi_key) @@ -845,7 +912,9 @@ def _extract_and_upload_rois(self): for roi_filename, roi_bytes in image_rois.items(): dst_client.create_file( - dst_bucket.url.bucket_name, os.path.join(dst_prefix, roi_filename), roi_bytes + dst_bucket.url.bucket_name, + compose_data_bucket_filename(self.escrow_address, self.chain_id, roi_filename), + roi_bytes, ) def _create_on_cvat(self): @@ -853,9 +922,7 @@ def _create_on_cvat(self): assert self.label_configuration is not self._not_configured input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) - oracle_bucket = self.oracle_data_bucket - oracle_bucket_prefix = self.oracle_data_bucket_prefix # Register cloud storage on CVAT to pass user dataset cloud_storage = cvat_api.create_cloudstorage( @@ -896,7 +963,14 @@ def _create_on_cvat(self): ), cvat_webhook_id=webhook.id, ) - db_service.add_project_images(session, project.id, list(self.roi_filenames.values())) + db_service.add_project_images( + session, + project.id, + [ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in self.roi_filenames.values() + ], + ) for job_filenames in self.job_layout: task = cvat_api.create_task(project.id, self.escrow_address) @@ -909,7 +983,10 @@ def _create_on_cvat(self): cvat_api.put_task_data( task.id, cloud_storage.id, - filenames=[os.path.join(oracle_bucket_prefix, fn) for fn in job_filenames], + filenames=[ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in job_filenames + ], sort_images=False, ) @@ -937,7 +1014,7 @@ def build(self): # Data preparation self._extract_and_upload_rois() - self._upload_config() + self._upload_meta() self._create_on_cvat() diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py new file mode 100644 index 0000000000..50bcb5152a --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -0,0 +1,447 @@ +import io +import os +import zipfile +from glob import glob +from tempfile import TemporaryDirectory +from typing import Dict, List, Sequence, Type + +import datumaro as dm +import numpy as np +from attrs import define +from defusedxml import ElementTree as ET + +from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.types import TaskType +from src.handlers.job_creation import DM_DATASET_FORMAT_MAPPING +from src.models.cvat import Image, Job +from src.services.cloud.utils import BucketAccessInfo +from src.utils.assignments import compose_data_bucket_filename +from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive + +CVAT_EXPORT_FORMAT_MAPPING = { + TaskType.image_label_binary: "CVAT for images 1.1", + TaskType.image_points: "CVAT for images 1.1", + TaskType.image_boxes: "COCO 1.0", + TaskType.image_boxes_from_points: "COCO 1.0", +} + +CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { + "CVAT for images 1.1": "cvat", + "COCO 1.0": "coco_instances", +} + + +@define +class FileDescriptor: + filename: str + file: io.RawIOBase + + +def prepare_annotation_metafile( + jobs: List[Job], job_annotations: Dict[int, FileDescriptor] +) -> FileDescriptor: + """ + Prepares a task/project annotation descriptor file with annotator mapping. + """ + + meta = AnnotationMeta( + jobs=[ + JobMeta( + job_id=job.cvat_id, + annotation_filename=job_annotations[job.cvat_id].filename, + annotator_wallet_address=job.latest_assignment.user_wallet_address, + assignment_id=job.latest_assignment.id, + ) + for job in jobs + ] + ) + + return FileDescriptor(ANNOTATION_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) + + +def flatten_points(input_points: List[dm.Points]) -> List[dm.Points]: + results = [] + + for pts in input_points: + for point_idx in range(len(pts.points) // 2): + point_x = pts.points[2 * point_idx + 0] + point_y = pts.points[2 * point_idx + 1] + results.append(dm.Points([point_x, point_y], label=pts.label)) + + return results + + +def fix_cvat_annotations(dataset_root: str): + for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): + with open(annotation_filename, "rb+") as f: + doc = ET.parse(f) + doc_root = doc.getroot() + + if doc_root.find("meta/project"): + # put labels into each task, if needed + # datumaro doesn't support /meta/project/ tag, but works with tasks, + # which is nested in the meta/project/ + labels_element = doc_root.find("meta/project/labels") + if not labels_element: + continue + + for task_element in doc_root.iterfind("meta/project/tasks/task"): + task_element.append(labels_element) + elif job_meta := doc_root.find("meta/job"): + # just rename the job into task for the same reasons + job_meta.tag = "task" + else: + continue + + f.seek(0) + f.truncate() + doc.write(f, encoding="utf-8") + + +def convert_point_arrays_dataset_to_1_point_skeletons( + dataset: dm.Dataset, labels: List[str] +) -> dm.Dataset: + def _get_skeleton_label(original_label: str) -> str: + return original_label + "_sk" + + new_label_cat = dm.LabelCategories.from_iterable( + [_get_skeleton_label(label) for label in labels] + + [(label, _get_skeleton_label(label)) for label in labels] + ) + new_points_cat = dm.PointsCategories.from_iterable( + (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels + ) + converted_dataset = dm.Dataset( + categories={ + dm.AnnotationType.label: new_label_cat, + dm.AnnotationType.points: new_points_cat, + }, + media_type=dm.Image, + ) + + label_id_map: Dict[int, int] = { + original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] + for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) + } # old id -> new id + + for sample in dataset: + points = [a for a in sample.annotations if isinstance(a, dm.Points)] + points = flatten_points(points) + + skeletons = [ + dm.Skeleton( + [p.wrap(label=label_id_map[p.label])], + label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], + ) + for p in points + ] + + converted_dataset.put(sample.wrap(annotations=skeletons)) + + return converted_dataset + + +def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]): + """ + Removes unknown images from the dataset inplace. + + On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, + with a suffix. We don't need these frames in the resulting dataset, + and we can safely remove them. + """ + if not isinstance(known_frames, set): + known_frames = set(known_frames) + + for sample in list(dataset): + item_image_filename = sample.media.path + + if item_image_filename not in known_frames: + dataset.remove(sample.id, sample.subset) + + +class _TaskProcessor: + def __init__( + self, + escrow_address: str, + chain_id: int, + annotations: List[FileDescriptor], + merged_annotation: FileDescriptor, + *, + manifest: TaskManifest, + project_images: List[Image], + ): + self.escrow_address = escrow_address + self.chain_id = chain_id + self.annotations = annotations + self.merged_annotation = merged_annotation + self.manifest = manifest + self.project_images = project_images + + def process(self): + with TemporaryDirectory() as tempdir: + for ann_descriptor in self.annotations: + if not zipfile.is_zipfile(ann_descriptor.file): + raise ValueError("Annotation files must be zip files") + ann_descriptor.file.seek(0) + + extract_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(ann_descriptor.filename))[0], + ) + extract_zip_archive(ann_descriptor.file, extract_dir) + + export_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(ann_descriptor.filename))[0] + "_conv", + ) + + self._process_annotation_file(ann_descriptor, extract_dir, export_dir) + + converted_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(export_dir, converted_dataset_archive) + converted_dataset_archive.seek(0) + + ann_descriptor.file = converted_dataset_archive + + def _process_annotation_file( + self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str + ): + task_type = self.manifest.annotation.type + input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] + resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] + + dataset = dm.Dataset.import_from(input_dir, input_format) + + if ann_descriptor.filename == self.merged_annotation.filename: + remove_duplicated_gt_frames( + dataset, + known_frames=[image.filename for image in self.project_images], + ) + + dataset.export(output_dir, resulting_format, save_images=False) + + +class _LabelsTaskProcessor(_TaskProcessor): + pass + + +class _BoxesTaskProcessor(_TaskProcessor): + pass + + +class _PointsTaskProcessor(_TaskProcessor): + def _process_annotation_file( + self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str + ): + # We need to convert point arrays, which cannot be represented in COCO directly, + # into the 1-point skeletons, compatible with COCO person keypoints, which is the + # required output format + task_type = self.manifest.annotation.type + input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] + resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] + + fix_cvat_annotations(input_dir) + dataset = dm.Dataset.import_from(input_dir, input_format) + + converted_dataset = convert_point_arrays_dataset_to_1_point_skeletons( + dataset, + labels=[label.name for label in self.manifest.annotation.labels], + ) + + if ann_descriptor.filename == self.merged_annotation.filename: + remove_duplicated_gt_frames( + converted_dataset, + known_frames=[image.filename for image in self.project_images], + ) + + converted_dataset.export(output_dir, resulting_format, save_images=False) + + +class _BoxesFromPointsTaskProcessor(_TaskProcessor): + def __init__(self, *args, **kwargs): + from src.handlers.job_creation import BoxesFromPointsTaskBuilder + + super().__init__(*args, **kwargs) + + roi_filenames, roi_infos, points_dataset = self._download_task_metadata() + + self.points_dataset = points_dataset + self.original_key_to_sample = {sample.attributes["id"]: sample for sample in points_dataset} + + roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + + self.roi_name_to_roi_id: Dict[str, BoxesFromPointsTaskBuilder.RoiInfo] = { + os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + def _download_task_metadata(self): + # TODO: refactor, move to domain/core + from src.handlers.job_creation import BoxesFromPointsTaskBuilder + from src.services.cloud import make_client as make_storage_client + + layout = BoxesFromPointsTaskBuilder.TaskMetaLayout() + serializer = BoxesFromPointsTaskBuilder.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + storage_client = make_storage_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + points_dataset = serializer.parse_points_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINTS_FILENAME + ), + ) + ) + + return roi_filenames, rois, points_dataset + + @staticmethod + def _shift_ann( + ann: dm.Annotation, offset_x: float, offset_y: float, img_w: int, img_h: int + ) -> dm.Annotation: + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann + + def _process_annotation_file( + self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str + ): + task_type = self.manifest.annotation.type + input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] + resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] + + roi_dataset = dm.Dataset.import_from(input_dir, input_format) + + if ann_descriptor.filename == self.merged_annotation.filename: + remove_duplicated_gt_frames( + roi_dataset, + known_frames=[image.filename for image in self.project_images], + ) + + merged_dataset = dm.Dataset( + media_type=dm.Image, + categories={ + dm.AnnotationType.label: dm.LabelCategories.from_iterable( + [label.name for label in self.manifest.annotation.labels] + ) + }, + ) + + for roi_sample in roi_dataset: + roi_info = self.roi_name_to_roi_id[os.path.basename(roi_sample.id)] + + original_sample = self.original_key_to_sample[roi_info.original_image_key] + image_h, image_w = original_sample.image.size + + merged_sample = merged_dataset.get(original_sample.id) + if not merged_sample: + merged_sample = original_sample.wrap(annotations=[]) + merged_dataset.put(merged_sample) + + old_point = next( + skeleton + for skeleton in original_sample.annotations + if skeleton.id == roi_info.point_id + ).elements[0] + old_x, old_y = old_point.points[:2] + + merged_sample.annotations.extend( + self._shift_ann( + roi_ann, + offset_x=old_x - roi_info.point_x, + offset_y=old_y - roi_info.point_y, + img_w=image_w, + img_h=image_h, + ) + for roi_ann in roi_sample.annotations + if isinstance(roi_ann, dm.Bbox) + ) + + merged_dataset.export(output_dir, resulting_format, save_images=False) + + +def postprocess_annotations( + escrow_address: str, + chain_id: int, + annotations: List[FileDescriptor], + merged_annotation: FileDescriptor, + *, + manifest: TaskManifest, + project_images: List[Image], +) -> None: + """ + Processes annotations and updates the files list inplace + """ + # TODO: remove complete duplicates + # TODO: restore original filenames and merge skeletons from RoIs + + processor_classes: Dict[TaskType, Type[_TaskProcessor]] = { + TaskType.image_label_binary: _LabelsTaskProcessor, + TaskType.image_boxes: _BoxesTaskProcessor, + TaskType.image_points: _PointsTaskProcessor, + TaskType.image_boxes_from_points: _BoxesFromPointsTaskProcessor, + } + + task_type = manifest.annotation.type + processor = processor_classes[task_type]( + escrow_address=escrow_address, + chain_id=chain_id, + annotations=annotations, + merged_annotation=merged_annotation, + manifest=manifest, + project_images=project_images, + ) + processor.process() diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index 29b3adcc1e..d03917bde1 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -13,7 +13,9 @@ def compose_assignment_url(task_id, job_id) -> str: return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}") -def compose_output_annotation_filename( - escrow_address: str, chain_id: Networks, filename: str -) -> str: +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" From 1281277cbb743fffa8775338f4d3f2476c8fa263 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 29 Jan 2024 11:02:37 +0200 Subject: [PATCH 05/82] Estimate max bbox --- .../cvat/exchange-oracle/src/handlers/job_creation.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 2d8a145336..ed2896c131 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -176,7 +176,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): # credentials=BucketCredentials() "Exchange Oracle's private bucket info" - self.min_class_samples_for_roi_estimation = 100 + self.min_class_samples_for_roi_estimation = 50 def __enter__(self): return self @@ -591,7 +591,7 @@ def _estimate_roi_sizes(self): ) ) - # Consider bbox sides as normally-distributed random variables, estimate sigmas + # Consider bbox sides as normally-distributed random variables, estimate max # For big enough datasets, it should be reasonable approximation # (due to the central limit theorem). This can work bad for small datasets, # so we only do this if there are enough class samples. @@ -602,9 +602,8 @@ def _estimate_roi_sizes(self): classes_with_default_roi.append(label_id) estimated_size = (2, 2) # 2 will yield just the image size after halving else: - mean_size = np.average(label_sizes, axis=0) - sigma = np.sqrt(np.var(label_sizes, axis=0)) - estimated_size = (mean_size + 3 * sigma) * self.roi_size_mult + max_bbox = np.max(label_sizes, axis=0) + estimated_size = max_bbox * self.roi_size_mult roi_size_estimations_per_label[label_id] = estimated_size From fffd19ce7b16423ed8e83fd1da3ecf628bb8a03d Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 29 Jan 2024 11:08:56 +0200 Subject: [PATCH 06/82] Little refactoring --- .../cvat/exchange-oracle/src/handlers/job_creation.py | 4 ++-- .../examples/cvat/exchange-oracle/src/handlers/job_export.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index ed2896c131..382a0ba18a 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -782,7 +782,7 @@ def parse_roi_filenames( ) -> BoxesFromPointsTaskBuilder.RoiFilenames: return {int(k): v for k, v in parse_json(roi_filenames_data).items()} - def _upload_meta(self): + def _upload_task_meta(self): # TODO: maybe extract into a separate function / class / library, # extract constants, serialization methods return TaskConfig from build() @@ -1013,7 +1013,7 @@ def build(self): # Data preparation self._extract_and_upload_rois() - self._upload_meta() + self._upload_task_meta() self._create_on_cvat() diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 50bcb5152a..3c352e7d07 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -265,7 +265,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - roi_filenames, roi_infos, points_dataset = self._download_task_metadata() + roi_filenames, roi_infos, points_dataset = self._download_task_meta() self.points_dataset = points_dataset self.original_key_to_sample = {sample.attributes["id"]: sample for sample in points_dataset} @@ -277,7 +277,7 @@ def __init__(self, *args, **kwargs): for roi_id, roi_filename in roi_filenames.items() } - def _download_task_metadata(self): + def _download_task_meta(self): # TODO: refactor, move to domain/core from src.handlers.job_creation import BoxesFromPointsTaskBuilder from src.services.cloud import make_client as make_storage_client From 585ef407d81821b10a586ea25f1c7992931dac46 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 29 Jan 2024 11:34:04 +0200 Subject: [PATCH 07/82] Copy some shared code from excor to recor --- .../cvat/exchange-oracle/src/core/storage.py | 10 ++ .../src/crons/state_trackers.py | 3 +- .../src/handlers/job_export.py | 2 +- .../exchange-oracle/src/utils/assignments.py | 9 -- .../cvat/recording-oracle/src/core/config.py | 1 + .../cvat/recording-oracle/src/core/storage.py | 10 ++ .../cvat/recording-oracle/src/core/types.py | 6 +- .../crons/process_exchange_oracle_webhooks.py | 19 ++-- .../src/services/cloud/__init__.py | 16 +--- .../src/services/cloud/client.py | 65 +++---------- .../recording-oracle/src/services/cloud/s3.py | 88 +++++++++++++++++ .../src/services/cloud/types.py | 44 +++++++++ .../src/services/cloud/utils.py | 96 +++++++++++++++++++ .../src/utils/cloud_storage.py | 64 ------------- .../recording-oracle/src/utils/storage.py | 5 - .../src/validation/dataset_comparison.py | 6 +- 16 files changed, 285 insertions(+), 159 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/core/storage.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/storage.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/s3.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/types.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/utils.py delete mode 100644 packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py delete mode 100644 packages/examples/cvat/recording-oracle/src/utils/storage.py diff --git a/packages/examples/cvat/exchange-oracle/src/core/storage.py b/packages/examples/cvat/exchange-oracle/src/core/storage.py new file mode 100644 index 0000000000..b934b865c0 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/storage.py @@ -0,0 +1,10 @@ +from src.core.config import Config +from src.core.types import Networks + + +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 71492dd0fc..b79ed8fbdb 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -12,6 +12,7 @@ ExchangeOracleEvent_TaskCreationFailed, ExchangeOracleEvent_TaskFinished, ) +from src.core.storage import compose_results_bucket_filename from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatus from src.db import SessionLocal from src.db.utils import ForUpdateParams @@ -22,7 +23,7 @@ prepare_annotation_metafile, ) from src.log import ROOT_LOGGER_NAME -from src.utils.assignments import compose_results_bucket_filename, parse_manifest +from src.utils.assignments import parse_manifest from src.utils.logging import get_function_logger module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 3c352e7d07..bbb4f31433 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -13,11 +13,11 @@ from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta from src.core.config import Config from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename from src.core.types import TaskType from src.handlers.job_creation import DM_DATASET_FORMAT_MAPPING from src.models.cvat import Image, Job from src.services.cloud.utils import BucketAccessInfo -from src.utils.assignments import compose_data_bucket_filename from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive CVAT_EXPORT_FORMAT_MAPPING = { diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index d03917bde1..8176203fee 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -2,7 +2,6 @@ from src.core.config import Config from src.core.manifest import TaskManifest -from src.core.types import Networks def parse_manifest(manifest: dict) -> TaskManifest: @@ -11,11 +10,3 @@ def parse_manifest(manifest: dict) -> TaskManifest: def compose_assignment_url(task_id, job_id) -> str: return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}") - - -def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: - return f"{escrow_address}@{chain_id}/{filename}" - - -def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: - return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 932596795c..a647973007 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -101,6 +101,7 @@ class ExchangeOracleStorageConfig: access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY", "") data_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") + results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") secure = str_to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) @classmethod diff --git a/packages/examples/cvat/recording-oracle/src/core/storage.py b/packages/examples/cvat/recording-oracle/src/core/storage.py new file mode 100644 index 0000000000..b934b865c0 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/storage.py @@ -0,0 +1,10 @@ +from src.core.config import Config +from src.core.types import Networks + + +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/core/types.py b/packages/examples/cvat/recording-oracle/src/core/types.py index 90ff668752..ab9f3492e3 100644 --- a/packages/examples/cvat/recording-oracle/src/core/types.py +++ b/packages/examples/cvat/recording-oracle/src/core/types.py @@ -14,6 +14,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" + image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" class OracleWebhookTypes(str, Enum): @@ -36,8 +37,3 @@ class ExchangeOracleEventType(str, Enum, metaclass=BetterEnumMeta): class RecordingOracleEventType(str, Enum, metaclass=BetterEnumMeta): task_completed = "task_completed" task_rejected = "task_rejected" - - -class CloudProviders(str, Enum, metaclass=BetterEnumMeta): - aws = "AWS_S3_BUCKET" - gcs = "GOOGLE_CLOUD_STORAGE" diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py index 1326152839..da8b0227e5 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py @@ -29,10 +29,13 @@ from src.log import ROOT_LOGGER_NAME from src.models.webhook import Webhook from src.services.cloud import download_file -from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest -from src.utils.cloud_storage import parse_bucket_url +from src.services.cloud.utils import parse_bucket_url +from src.utils.assignments import ( + compose_results_bucket_filename, + compute_resulting_annotations_hash, + parse_manifest, +) from src.utils.logging import get_function_logger -from src.utils.storage import compose_bucket_filename from src.utils.webhooks import prepare_outgoing_webhook_body, prepare_signed_message module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" @@ -96,7 +99,7 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge excor_bucket_host = Config.exchange_oracle_storage_config.provider_endpoint_url() excor_bucket_name = Config.exchange_oracle_storage_config.data_bucket_name - excor_annotation_meta_path = compose_bucket_filename( + excor_annotation_meta_path = compose_results_bucket_filename( webhook.escrow_address, webhook.chain_id, annotation.ANNOTATION_METAFILE_NAME, @@ -108,7 +111,7 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge job_annotations: Dict[int, bytes] = {} for job_meta in annotation_meta.jobs: - job_filename = compose_bucket_filename( + job_filename = compose_results_bucket_filename( webhook.escrow_address, webhook.chain_id, job_meta.annotation_filename, @@ -127,7 +130,7 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge gt_filename, ) - excor_merged_annotation_path = compose_bucket_filename( + excor_merged_annotation_path = compose_results_bucket_filename( webhook.escrow_address, webhook.chain_id, annotation.RESULTING_ANNOTATIONS_FILE, @@ -154,13 +157,13 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge f"average annotation quality is {validation_results.average_quality:.2f}" ) - recor_merged_annotations_path = compose_bucket_filename( + recor_merged_annotations_path = compose_results_bucket_filename( webhook.escrow_address, webhook.chain_id, validation.RESULTING_ANNOTATIONS_FILE, ) - recor_validation_meta_path = compose_bucket_filename( + recor_validation_meta_path = compose_results_bucket_filename( webhook.escrow_address, webhook.chain_id, validation.VALIDATION_METAFILE_NAME, diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py index eb128cc100..007394d9ed 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py @@ -1,13 +1,3 @@ -from typing import Optional - -from src.services.cloud.client import S3Client - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_fileobj(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, path: Optional[str] = None) -> list[str]: - client = S3Client(bucket_host) - return [f.key for f in client.list_files(bucket_name, path=path)] +from src.services.cloud.client import StorageClient +from src.services.cloud.types import CloudProviders +from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py index 2c6101a573..0a80d1706e 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py @@ -1,61 +1,24 @@ -# Copyright (C) 2022 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - -from io import BytesIO +from abc import ABCMeta, abstractmethod from typing import List, Optional -import boto3 -from botocore.exceptions import ClientError -from botocore.handlers import disable_signing - - -class S3Client: - def __init__( - self, - endpoint_url: str, - *, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - ) -> None: - s3 = boto3.resource( - "s3", - **(dict(aws_access_key_id=access_key) if access_key else {}), - **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=endpoint_url, - ) - - self.resource = s3 - self.client = s3.meta.client - - if not access_key and not secret_key: - self.client.meta.events.register("choose-signer.s3.*", disable_signing) +class StorageClient(metaclass=ABCMeta): + @abstractmethod def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=bucket, Key=filename) + ... + @abstractmethod def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=bucket, Key=filename) + ... + @abstractmethod def file_exists(self, bucket: str, filename: str) -> bool: - try: - self.client.head_object(Bucket=bucket, Key=filename) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - else: - raise + ... - def download_fileobj(self, bucket: str, key: str) -> bytes: - with BytesIO() as data: - self.client.download_fileobj(Bucket=bucket, Key=key, Fileobj=data) - return data.getvalue() + @abstractmethod + def download_file(self, bucket: str, key: str) -> bytes: + ... - def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(bucket).objects - if path: - objects = objects.filter(Prefix=path.strip("/\\") + "/") - else: - objects = objects.all() - return list(objects) + @abstractmethod + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + ... diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py new file mode 100644 index 0000000000..ed00f2799e --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from dataclasses import dataclass +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +import boto3 +from botocore.exceptions import ClientError +from botocore.handlers import disable_signing + +from src.services.cloud.client import StorageClient +from src.services.cloud.types import BucketCredentials + + +class S3Client(StorageClient): + def __init__( + self, + endpoint_url: str, + *, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + ) -> None: + s3 = boto3.resource( + "s3", + **(dict(aws_access_key_id=access_key) if access_key else {}), + **(dict(aws_secret_access_key=secret_key) if secret_key else {}), + endpoint_url=unquote(endpoint_url), + ) + + self.resource = s3 + self.client = s3.meta.client + + if not access_key and not secret_key: + self.client.meta.events.register("choose-signer.s3.*", disable_signing) + + def create_file(self, bucket: str, filename: str, data: bytes = b""): + self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + + def remove_file(self, bucket: str, filename: str): + self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + + def file_exists(self, bucket: str, filename: str) -> bool: + try: + self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return False + else: + raise + + def download_file(self, bucket: str, key: str) -> bytes: + with BytesIO() as data: + self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) + return data.getvalue() + + def list_files(self, bucket: str, *, prefix: Optional[str] = None) -> List: + objects = self.resource.Bucket(unquote(bucket)).objects + if prefix: + objects = objects.filter(Prefix=unquote(prefix).strip("/\\") + "/") + else: + objects = objects.all() + return list(objects) + + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + return [file_info.key for file_info in self.list_files(bucket=bucket, prefix=prefix)] + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +DEFAULT_S3_HOST = "s3.amazonaws.com" + + +def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: + client = S3Client(bucket_host) + return client.download_file(bucket_name, filename) + + +def list_files(bucket_host: str, bucket_name: str, *, prefix: Optional[str] = None) -> List[str]: + client = S3Client(bucket_host) + return client.list_filenames(bucket_name, prefix=prefix) diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py new file mode 100644 index 0000000000..ee89883d52 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum, auto +from typing import Optional + +from src.utils.enums import BetterEnumMeta + + +class CloudProviders(Enum, metaclass=BetterEnumMeta): + aws = auto() + gcs = auto() + + +@dataclass +class BucketUrl: + provider: CloudProviders + host_url: str + bucket_name: str + path: str + + +class BucketCredentials: + pass + + +@dataclass +class BucketAccessInfo: + url: BucketUrl + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_raw_url(cls, url: str) -> BucketAccessInfo: + from src.services.cloud.utils import parse_bucket_url + + return cls.from_parsed_url(parse_bucket_url(url)) + + @classmethod + def from_parsed_url(cls, parsed_url: BucketUrl) -> BucketAccessInfo: + return BucketAccessInfo(url=parsed_url) + + @property + def provider(self) -> CloudProviders: + return self.url.provider diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py new file mode 100644 index 0000000000..33b9a4f7ee --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py @@ -0,0 +1,96 @@ +from typing import Optional, Union, overload +from urllib.parse import urlparse + +from src.core.config import Config +from src.services.cloud.client import StorageClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, BucketUrl, CloudProviders +from src.utils.net import is_ipv4 + + +def parse_bucket_url(data_url: str) -> BucketUrl: + parsed_url = urlparse(data_url) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketUrl( + provider=CloudProviders.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + # elif parsed_url.netloc.endswith("storage.googleapis.com"): + # # Google Cloud Storage (GCS) bucket + # return ParsedBucketUrl( + # provider=CloudProviders.gcs, + # bucket_name=parsed_url.netloc.split(".")[0], + # ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketUrl( + provider=CloudProviders.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + + +def compose_bucket_url( + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None +) -> str: + match provider: + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" + + +@overload +def make_client(url: BucketUrl, credentials: Optional[BucketCredentials] = None) -> StorageClient: + ... + + +@overload +def make_client( + bucket_info: BucketAccessInfo, +) -> StorageClient: + ... + + +def make_client( + _pos1: Union[BucketUrl, BucketAccessInfo, None] = None, + *, + bucket_info: Optional[BucketAccessInfo] = None, + url: Optional[BucketUrl] = None, + credentials: Optional[BucketCredentials] = None, +) -> StorageClient: + if _pos1 is not None: + if isinstance(_pos1, BucketAccessInfo): + bucket_info = _pos1 + else: + url = _pos1 + + if bucket_info is None: + bucket_info = BucketAccessInfo(url=url, credentials=credentials) + + match bucket_info.provider: + case CloudProviders.aws: + client_kwargs = {} + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + client = S3Client(bucket_info.url.host_url, **client_kwargs) + case _: + raise Exception("Unsupported cloud provider") + + return client diff --git a/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py deleted file mode 100644 index 3418fde5bc..0000000000 --- a/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py +++ /dev/null @@ -1,64 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from urllib.parse import urlparse - -from src.core.config import Config -from src.core.types import CloudProviders -from src.utils.net import is_ipv4 - - -@dataclass -class ParsedBucketUrl: - provider: str - host_url: str - bucket_name: str - path: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def parse_bucket_url(data_url: str) -> ParsedBucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs.value, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") - - -def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None -) -> str: - match provider: - case CloudProviders.aws.value: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs.value: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" diff --git a/packages/examples/cvat/recording-oracle/src/utils/storage.py b/packages/examples/cvat/recording-oracle/src/utils/storage.py deleted file mode 100644 index 2ce5d70eec..0000000000 --- a/packages/examples/cvat/recording-oracle/src/utils/storage.py +++ /dev/null @@ -1,5 +0,0 @@ -from src.core.types import Networks - - -def compose_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: - return f"{escrow_address}@{chain_id}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 9ecb640060..884478ef56 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -1,9 +1,10 @@ import itertools +from abc import ABCMeta, abstractmethod from typing import Any, Callable, Dict, Tuple import datumaro as dm import numpy as np -from attrs import define, field +from attrs import define from .annotation_matching import Bbox, Point, bbox_iou, match_annotations, point_to_bbox_cmp @@ -31,9 +32,10 @@ def __init__(self, inner: Callable) -> None: @define -class DatasetComparator: +class DatasetComparator(metaclass=ABCMeta): min_similarity_threshold: float + @abstractmethod def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: ... From 03ada68190d6306d0debd27f37aa08becb60bcbd Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 29 Jan 2024 13:10:31 +0200 Subject: [PATCH 08/82] Refactor task creation and export --- .../src/core/tasks/__init__.py | 0 .../src/core/tasks/boxes_from_points.py | 92 +++++++ .../src/handlers/job_creation.py | 197 ++++---------- .../src/handlers/job_export.py | 255 ++++-------------- .../exchange-oracle/src/utils/annotations.py | 160 +++++++++++ 5 files changed, 361 insertions(+), 343 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py create mode 100644 packages/examples/cvat/exchange-oracle/src/utils/annotations.py diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py new file mode 100644 index 0000000000..c9320473b6 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +BboxPointMapping = Dict[int, int] + + +@frozen +class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return (Path(gt_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_bbox_point_mapping(self, bbox_point_mapping: BboxPointMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping(self, bbox_point_mapping_data: bytes) -> BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 382a0ba18a..f6bfa99e62 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -1,6 +1,5 @@ from __future__ import annotations -import io import os import random import uuid @@ -8,32 +7,33 @@ from itertools import groupby from logging import Logger from math import ceil -from pathlib import Path from tempfile import TemporaryDirectory from typing import Dict, List, Sequence, Tuple, Union, cast -import attrs import cv2 import datumaro as dm import numpy as np -from attrs import frozen -from datumaro.util import dump_json, parse_json, take_by +from datumaro.util import take_by from datumaro.util.image import IMAGE_EXTENSIONS, decode_image, encode_image +import src.core.tasks.boxes_from_points as boxes_from_points_task import src.cvat.api_calls as cvat_api import src.services.cloud as cloud_service import src.services.cvat as db_service from src.chain.escrow import get_escrow_manifest from src.core.config import Config from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename from src.core.types import CvatLabelType, TaskStatus, TaskType from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME from src.services.cloud import CloudProviders, StorageClient from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url -from src.utils.assignments import compose_data_bucket_filename, parse_manifest +from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger +module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" + LABEL_TYPE_MAPPING = { TaskType.image_label_binary: CvatLabelType.tag, TaskType.image_points: CvatLabelType.points, @@ -61,57 +61,33 @@ CloudProviders.gcs: "GOOGLE_CLOUD_STORAGE", } -module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" +class DatasetValidationError(Exception): + pass -class BoxesFromPointsTaskBuilder: - class _NotConfigured: - def __bool__(self) -> bool: - return False - _not_configured = _NotConfigured() +class MismatchingAnnotations(DatasetValidationError): + pass - class DatasetValidationError(Exception): - pass - class MismatchingAnnotations(DatasetValidationError): - pass +class TooFewSamples(DatasetValidationError): + pass - class TooFewSamples(DatasetValidationError): - pass - class TooManyBoxesDiscarded(DatasetValidationError): - pass +class InvalidCategories(DatasetValidationError): + pass - class InvalidCategories(DatasetValidationError): - pass - class InvalidImageInfo(DatasetValidationError): - pass +class InvalidImageInfo(DatasetValidationError): + pass - BboxPointMapping = Dict[int, int] - @frozen - class RoiInfo: - point_id: int - original_image_key: int - point_x: int - point_y: int - roi_x: int - roi_y: int - roi_w: int - roi_h: int - - def asdict(self) -> dict: - return attrs.asdict(self, recurse=False) - - RoiInfos = Sequence[RoiInfo] - - RoiFilenames = Dict[int, str] +class BoxesFromPointsTaskBuilder: + class _NotConfigured: + def __bool__(self) -> bool: + return False - max_discarded_threshold = 0.5 - "The maximum allowed percent of discarded " - "GT boxes, points, or samples for successful job launch" + _not_configured = _NotConfigured() def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.exit_stack = ExitStack() @@ -133,7 +109,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.gt_dataset: Union[dm.Dataset, self._NotConfigured] = self._not_configured self.bbox_point_mapping: Union[ - self.BboxPointMapping, self._NotConfigured + boxes_from_points_task.BboxPointMapping, self._NotConfigured ] = self._not_configured "bbox_id -> point_id" @@ -142,8 +118,12 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): ] = self._not_configured "label_id -> (rel. w, rel. h)" - self.rois: Union[self.RoiInfos, self._NotConfigured] = self._not_configured - self.roi_filenames: Union[self.RoiFilenames, self._NotConfigured] = self._not_configured + self.rois: Union[ + boxes_from_points_task.RoiInfos, self._NotConfigured + ] = self._not_configured + self.roi_filenames: Union[ + boxes_from_points_task.RoiFilenames, self._NotConfigured + ] = self._not_configured self.job_layout: Union[Sequence[Sequence[str]], self._NotConfigured] = self._not_configured "File lists per CVAT job" @@ -178,6 +158,12 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.min_class_samples_for_roi_estimation = 50 + self.max_discarded_threshold = 0.5 + """ + The maximum allowed percent of discarded + GT boxes, points, or samples for successful job launch + """ + def __enter__(self): return self @@ -249,7 +235,7 @@ def _validate_gt_labels(self): ) manifest_labels = set(label.name for label in self.manifest.annotation.labels) if gt_labels - manifest_labels: - raise self.DatasetValidationError( + raise DatasetValidationError( "GT labels do not match job labels. Unknown labels: {}".format( self._format_list(gt_labels - manifest_labels), ) @@ -269,14 +255,14 @@ def _validate_gt_filenames(self): if len(gt_filenames) != len(matched_gt_filenames): extra_gt = list(map(os.path.basename, gt_filenames - matched_gt_filenames)) - raise self.MismatchingAnnotations( + raise MismatchingAnnotations( "Failed to find several validation samples in the dataset files: {}".format( self._format_list(extra_gt) ) ) if len(gt_filenames) < self.manifest.validation.val_size: - raise self.TooFewSamples( + raise TooFewSamples( f"Too few validation samples provided ({len(gt_filenames)}), " f"at least {self.manifest.validation.val_size} required." ) @@ -319,7 +305,7 @@ def _validate_points_categories(self): ) if invalid_point_categories_messages: - raise self.InvalidCategories( + raise InvalidCategories( "Invalid categories in the input point annotations: {}".format( self._format_list(invalid_point_categories_messages, separator="; ") ) @@ -328,7 +314,7 @@ def _validate_points_categories(self): points_labels = set(label.name for label in points_dataset_label_cat if not label.parent) manifest_labels = set(label.name for label in self.manifest.annotation.labels) if manifest_labels != points_labels: - raise self.DatasetValidationError("Point labels do not match job labels") + raise DatasetValidationError("Point labels do not match job labels") self.input_points_dataset.transform( "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] @@ -349,7 +335,7 @@ def _validate_points_filenames(self): break if filenames_with_invalid_points: - raise self.MismatchingAnnotations( + raise MismatchingAnnotations( "Some images have invalid points: {}".format( self._format_list(filenames_with_invalid_points) ) @@ -366,7 +352,7 @@ def _validate_points_filenames(self): map(os.path.basename, points_filenames - matched_points_filenames) ) - raise self.MismatchingAnnotations( + raise MismatchingAnnotations( "Mismatching points info and input files: {}".format( "; ".join( "{} missing points".format(self._format_list(missing_point_samples)), @@ -402,7 +388,7 @@ def _validate_points_annotations(self): excluded_samples.append(((sample.id, sample.subset), message)) if len(excluded_samples) > len(self.input_points_dataset) * self.max_discarded_threshold: - raise self.DatasetValidationError( + raise DatasetValidationError( "Too many samples discarded, canceling job creation. Errors: {}".format( self._format_list([message for _, message in excluded_samples]) ) @@ -548,7 +534,7 @@ def _prepare_gt(self): gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) if len(bbox_point_mapping) < (1 - self.max_discarded_threshold) * total_boxes: - raise self.TooManyBoxesDiscarded( + raise DatasetValidationError( "Too many GT boxes discarded ({} out of {}). " "Please make sure each GT box matches exactly 1 point".format( total_boxes - len(bbox_point_mapping), total_boxes @@ -563,7 +549,7 @@ def _prepare_gt(self): if not label_count ] if gt_labels_without_anns: - raise self.DatasetValidationError( + raise DatasetValidationError( "No matching GT boxes/points annotations found for some classes: {}".format( self._format_list(gt_labels_without_anns) ) @@ -625,7 +611,7 @@ def _prepare_roi_info(self): assert self.roi_size_estimations is not self._not_configured assert self.input_points_dataset is not self._not_configured - rois: List[self.RoiInfo] = [] + rois: List[boxes_from_points_task.RoiInfo] = [] for sample in self.input_points_dataset: for skeleton in sample.annotations: if not isinstance(skeleton, dm.Skeleton): @@ -654,7 +640,7 @@ def _prepare_roi_info(self): new_point_y = original_point_y - roi_top rois.append( - self.RoiInfo( + boxes_from_points_task.RoiInfo( point_id=skeleton.id, original_image_key=sample.attributes["id"], point_x=new_point_x, @@ -708,86 +694,12 @@ def _prepare_job_layout(self): def _prepare_label_configuration(self): self.label_configuration = make_label_configuration(self.manifest) - class TaskMetaLayout: - GT_FILENAME = "gt.json" - POINTS_FILENAME = "points.json" - BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" - ROI_INFO_FILENAME = "rois.json" - - ROI_FILENAMES_FILENAME = "roi_filenames.json" - # this is separated from the general roi info to make name mangling more "optional" - - class TaskMetaSerializer: - GT_DATASET_FORMAT = "coco_instances" - POINTS_DATASET_FORMAT = "coco_person_keypoints" - - def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: - with TemporaryDirectory() as temp_dir: - gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") - gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) - return ( - Path(gt_dataset_dir) / "annotations" / "instances_default.json" - ).read_bytes() - - def serialize_bbox_point_mapping( - self, bbox_point_mapping: BoxesFromPointsTaskBuilder.BboxPointMapping - ) -> bytes: - return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) - - def serialize_roi_info(self, rois_info: BoxesFromPointsTaskBuilder.RoiInfos) -> bytes: - return dump_json([roi_info.asdict() for roi_info in rois_info]) - - def serialize_roi_filenames( - self, roi_filenames: BoxesFromPointsTaskBuilder.RoiFilenames - ) -> bytes: - return dump_json({str(k): v for k, v in roi_filenames.items()}) - - def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: - with TemporaryDirectory() as temp_dir: - annotations_filename = os.path.join(temp_dir, "annotations.json") - with open(annotations_filename, "wb") as f: - f.write(gt_dataset_data) - - dataset = dm.Dataset.import_from( - annotations_filename, format=self.GT_DATASET_FORMAT - ) - dataset.init_cache() - return dataset - - def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: - with TemporaryDirectory() as temp_dir: - annotations_filename = os.path.join(temp_dir, "annotations.json") - with open(annotations_filename, "wb") as f: - f.write(points_dataset_data) - - dataset = dm.Dataset.import_from( - annotations_filename, format=self.POINTS_DATASET_FORMAT - ) - dataset.init_cache() - return dataset - - def parse_bbox_point_mapping( - self, bbox_point_mapping_data: bytes - ) -> BoxesFromPointsTaskBuilder.BboxPointMapping: - return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} - - def parse_roi_info(self, rois_info_data: bytes) -> BoxesFromPointsTaskBuilder.RoiInfos: - return [ - BoxesFromPointsTaskBuilder.RoiInfo(**roi_info) - for roi_info in parse_json(rois_info_data) - ] - - def parse_roi_filenames( - self, roi_filenames_data: bytes - ) -> BoxesFromPointsTaskBuilder.RoiFilenames: - return {int(k): v for k, v in parse_json(roi_filenames_data).items()} - def _upload_task_meta(self): # TODO: maybe extract into a separate function / class / library, # extract constants, serialization methods return TaskConfig from build() - layout = self.TaskMetaLayout() - serializer = self.TaskMetaSerializer() + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() file_list = [] file_list.append((self.input_points_data, layout.POINTS_FILENAME)) @@ -817,7 +729,9 @@ def _upload_task_meta(self): file_data, ) - def _draw_roi_point(self, roi_pixels: np.ndarray, roi_info: RoiInfo) -> np.ndarray: + def _draw_roi_point( + self, roi_pixels: np.ndarray, roi_info: boxes_from_points_task.RoiInfo + ) -> np.ndarray: center = (roi_info.point_x, roi_info.point_y) roi_r = (roi_info.roi_w**2 + roi_info.roi_h**2) ** 0.5 / 2 @@ -869,7 +783,7 @@ def _extract_and_upload_rois(self): filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} _roi_key = lambda e: e.original_image_key - rois_by_image: Dict[str, self.RoiInfo] = { + rois_by_image: Dict[str, Sequence[boxes_from_points_task.RoiInfo]] = { image_id_to_filename[image_id]: list(g) for image_id, g in groupby(sorted(self.rois, key=_roi_key), key=_roi_key) } @@ -889,13 +803,12 @@ def _extract_and_upload_rois(self): # TODO: maybe rois should be regenerated instead # Option 2: accumulate errors, fail when some threshold is reached # Option 3: add special handling for cases when image is only rotated (exif etc.) - raise self.InvalidImageInfo( + raise InvalidImageInfo( f"Sample '{filename}': invalid size provided in the point annotations" ) image_rois = {} for roi_info in image_roi_infos: - roi_info = cast(self.RoiInfo, roi_info) roi_pixels = image_pixels[ roi_info.roi_y : roi_info.roi_y + roi_info.roi_h, roi_info.roi_x : roi_info.roi_x + roi_info.roi_w, @@ -1040,7 +953,7 @@ def get_gt_filenames( missing_gt = gt_filenames - matched_gt_filenames missing_gt_display_threshold = 10 remainder = len(missing_gt) - missing_gt_display_threshold - raise Exception( + raise DatasetValidationError( "Failed to find several validation samples in the dataset files: {}{}".format( ", ".join(missing_gt[:missing_gt_display_threshold]), f"(and {remainder} more)" if remainder else "", @@ -1048,7 +961,7 @@ def get_gt_filenames( ) if len(gt_filenames) < manifest.validation.val_size: - raise Exception( + raise TooFewSamples( f"Too few validation samples provided ({len(gt_filenames)}), " f"at least {manifest.validation.val_size} required." ) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index bbb4f31433..706a6c9b00 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -1,15 +1,15 @@ import io import os import zipfile -from glob import glob from tempfile import TemporaryDirectory -from typing import Dict, List, Sequence, Type +from typing import Dict, List, Type import datumaro as dm -import numpy as np from attrs import define -from defusedxml import ElementTree as ET +from datumaro.components.dataset import Dataset +import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.utils.annotations as annotation_utils from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta from src.core.config import Config from src.core.manifest import TaskManifest @@ -17,6 +17,7 @@ from src.core.types import TaskType from src.handlers.job_creation import DM_DATASET_FORMAT_MAPPING from src.models.cvat import Image, Job +from src.services.cloud import make_client as make_cloud_client from src.services.cloud.utils import BucketAccessInfo from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive @@ -61,106 +62,6 @@ def prepare_annotation_metafile( return FileDescriptor(ANNOTATION_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) -def flatten_points(input_points: List[dm.Points]) -> List[dm.Points]: - results = [] - - for pts in input_points: - for point_idx in range(len(pts.points) // 2): - point_x = pts.points[2 * point_idx + 0] - point_y = pts.points[2 * point_idx + 1] - results.append(dm.Points([point_x, point_y], label=pts.label)) - - return results - - -def fix_cvat_annotations(dataset_root: str): - for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): - with open(annotation_filename, "rb+") as f: - doc = ET.parse(f) - doc_root = doc.getroot() - - if doc_root.find("meta/project"): - # put labels into each task, if needed - # datumaro doesn't support /meta/project/ tag, but works with tasks, - # which is nested in the meta/project/ - labels_element = doc_root.find("meta/project/labels") - if not labels_element: - continue - - for task_element in doc_root.iterfind("meta/project/tasks/task"): - task_element.append(labels_element) - elif job_meta := doc_root.find("meta/job"): - # just rename the job into task for the same reasons - job_meta.tag = "task" - else: - continue - - f.seek(0) - f.truncate() - doc.write(f, encoding="utf-8") - - -def convert_point_arrays_dataset_to_1_point_skeletons( - dataset: dm.Dataset, labels: List[str] -) -> dm.Dataset: - def _get_skeleton_label(original_label: str) -> str: - return original_label + "_sk" - - new_label_cat = dm.LabelCategories.from_iterable( - [_get_skeleton_label(label) for label in labels] - + [(label, _get_skeleton_label(label)) for label in labels] - ) - new_points_cat = dm.PointsCategories.from_iterable( - (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels - ) - converted_dataset = dm.Dataset( - categories={ - dm.AnnotationType.label: new_label_cat, - dm.AnnotationType.points: new_points_cat, - }, - media_type=dm.Image, - ) - - label_id_map: Dict[int, int] = { - original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] - for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) - } # old id -> new id - - for sample in dataset: - points = [a for a in sample.annotations if isinstance(a, dm.Points)] - points = flatten_points(points) - - skeletons = [ - dm.Skeleton( - [p.wrap(label=label_id_map[p.label])], - label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], - ) - for p in points - ] - - converted_dataset.put(sample.wrap(annotations=skeletons)) - - return converted_dataset - - -def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]): - """ - Removes unknown images from the dataset inplace. - - On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, - with a suffix. We don't need these frames in the resulting dataset, - and we can safely remove them. - """ - if not isinstance(known_frames, set): - known_frames = set(known_frames) - - for sample in list(dataset): - item_image_filename = sample.media.path - - if item_image_filename not in known_frames: - dataset.remove(sample.id, sample.subset) - - class _TaskProcessor: def __init__( self, @@ -179,6 +80,11 @@ def __init__( self.manifest = manifest self.project_images = project_images + self.input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[ + CVAT_EXPORT_FORMAT_MAPPING[manifest.annotation.type] + ] + self.output_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + def process(self): with TemporaryDirectory() as tempdir: for ann_descriptor in self.annotations: @@ -208,19 +114,31 @@ def process(self): def _process_annotation_file( self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str ): - task_type = self.manifest.annotation.type - input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] - resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] + input_dataset = self._parse_dataset(ann_descriptor, input_dir) + output_dataset = self._process_dataset(input_dataset, ann_descriptor=ann_descriptor) + self._export_dataset(output_dataset, output_dir) + + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> dm.Dataset: + return dm.Dataset.import_from(dataset_dir, self.input_format) - dataset = dm.Dataset.import_from(input_dir, input_format) + def _export_dataset(self, dataset: dm.Dataset, output_dir: str): + dataset.export(output_dir, self.output_format, save_images=False) + + def _process_dataset( + self, dataset: dm.Dataset, *, ann_descriptor: FileDescriptor + ) -> dm.Dataset: + # TODO: remove complete duplicates in annotations if ann_descriptor.filename == self.merged_annotation.filename: - remove_duplicated_gt_frames( - dataset, - known_frames=[image.filename for image in self.project_images], - ) + dataset = self._process_merged_dataset(dataset) - dataset.export(output_dir, resulting_format, save_images=False) + return dataset + + def _process_merged_dataset(self, input_dataset: dm.Dataset) -> dm.Dataset: + return annotation_utils.remove_duplicated_gt_frames( + input_dataset, + known_frames=[image.filename for image in self.project_images], + ) class _LabelsTaskProcessor(_TaskProcessor): @@ -232,37 +150,24 @@ class _BoxesTaskProcessor(_TaskProcessor): class _PointsTaskProcessor(_TaskProcessor): - def _process_annotation_file( - self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str - ): + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> Dataset: + annotation_utils.prepare_cvat_annotations_for_dm(dataset_dir) + return super()._parse_dataset(ann_descriptor, dataset_dir) + + def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) -> Dataset: # We need to convert point arrays, which cannot be represented in COCO directly, # into the 1-point skeletons, compatible with COCO person keypoints, which is the # required output format - task_type = self.manifest.annotation.type - input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] - resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] - - fix_cvat_annotations(input_dir) - dataset = dm.Dataset.import_from(input_dir, input_format) - - converted_dataset = convert_point_arrays_dataset_to_1_point_skeletons( + dataset = annotation_utils.convert_point_arrays_dataset_to_1_point_skeletons( dataset, labels=[label.name for label in self.manifest.annotation.labels], ) - if ann_descriptor.filename == self.merged_annotation.filename: - remove_duplicated_gt_frames( - converted_dataset, - known_frames=[image.filename for image in self.project_images], - ) - - converted_dataset.export(output_dir, resulting_format, save_images=False) + return super()._process_dataset(dataset, ann_descriptor=ann_descriptor) class _BoxesFromPointsTaskProcessor(_TaskProcessor): def __init__(self, *args, **kwargs): - from src.handlers.job_creation import BoxesFromPointsTaskBuilder - super().__init__(*args, **kwargs) roi_filenames, roi_infos, points_dataset = self._download_task_meta() @@ -272,25 +177,21 @@ def __init__(self, *args, **kwargs): roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} - self.roi_name_to_roi_id: Dict[str, BoxesFromPointsTaskBuilder.RoiInfo] = { + self.roi_name_to_roi_id: Dict[str, boxes_from_points_task.RoiInfo] = { os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] for roi_id, roi_filename in roi_filenames.items() } def _download_task_meta(self): - # TODO: refactor, move to domain/core - from src.handlers.job_creation import BoxesFromPointsTaskBuilder - from src.services.cloud import make_client as make_storage_client - - layout = BoxesFromPointsTaskBuilder.TaskMetaLayout() - serializer = BoxesFromPointsTaskBuilder.TaskMetaSerializer() + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) # TODO: add # credentials=BucketCredentials() "Exchange Oracle's private bucket info" - storage_client = make_storage_client(oracle_data_bucket) + storage_client = make_cloud_client(oracle_data_bucket) roi_filenames = serializer.parse_roi_filenames( storage_client.download_file( @@ -321,57 +222,10 @@ def _download_task_meta(self): return roi_filenames, rois, points_dataset - @staticmethod - def _shift_ann( - ann: dm.Annotation, offset_x: float, offset_y: float, img_w: int, img_h: int - ) -> dm.Annotation: - if isinstance(ann, dm.Bbox): - shifted_ann = ann.wrap( - x=offset_x + ann.x, - y=offset_y + ann.y, - ) - elif isinstance(ann, dm.Points): - shifted_ann = ann.wrap( - points=np.clip( - np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), - 0, - [img_w, img_h], - ).flat - ) - elif isinstance(ann, dm.Skeleton): - shifted_ann = ann.wrap( - elements=[ - point.wrap( - points=np.clip( - np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), - 0, - [img_w, img_h], - ).flat - ) - for point in ann.elements - ] - ) - else: - assert False, f"Unsupported annotation type '{ann.type}'" - - return shifted_ann - - def _process_annotation_file( - self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str - ): - task_type = self.manifest.annotation.type - input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] - resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] - - roi_dataset = dm.Dataset.import_from(input_dir, input_format) + def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: + point_roi_dataset = super()._process_merged_dataset(input_dataset) - if ann_descriptor.filename == self.merged_annotation.filename: - remove_duplicated_gt_frames( - roi_dataset, - known_frames=[image.filename for image in self.project_images], - ) - - merged_dataset = dm.Dataset( + merged_sample_dataset = dm.Dataset( media_type=dm.Image, categories={ dm.AnnotationType.label: dm.LabelCategories.from_iterable( @@ -380,16 +234,16 @@ def _process_annotation_file( }, ) - for roi_sample in roi_dataset: + for roi_sample in point_roi_dataset: roi_info = self.roi_name_to_roi_id[os.path.basename(roi_sample.id)] - original_sample = self.original_key_to_sample[roi_info.original_image_key] - image_h, image_w = original_sample.image.size - merged_sample = merged_dataset.get(original_sample.id) + merged_sample = merged_sample_dataset.get(original_sample.id) if not merged_sample: merged_sample = original_sample.wrap(annotations=[]) - merged_dataset.put(merged_sample) + merged_sample_dataset.put(merged_sample) + + image_h, image_w = merged_sample.image.size old_point = next( skeleton @@ -399,7 +253,7 @@ def _process_annotation_file( old_x, old_y = old_point.points[:2] merged_sample.annotations.extend( - self._shift_ann( + annotation_utils.shift_ann( roi_ann, offset_x=old_x - roi_info.point_x, offset_y=old_y - roi_info.point_y, @@ -410,7 +264,7 @@ def _process_annotation_file( if isinstance(roi_ann, dm.Bbox) ) - merged_dataset.export(output_dir, resulting_format, save_images=False) + return merged_sample_dataset def postprocess_annotations( @@ -425,9 +279,6 @@ def postprocess_annotations( """ Processes annotations and updates the files list inplace """ - # TODO: remove complete duplicates - # TODO: restore original filenames and merge skeletons from RoIs - processor_classes: Dict[TaskType, Type[_TaskProcessor]] = { TaskType.image_label_binary: _LabelsTaskProcessor, TaskType.image_boxes: _BoxesTaskProcessor, @@ -435,6 +286,8 @@ def postprocess_annotations( TaskType.image_boxes_from_points: _BoxesFromPointsTaskProcessor, } + # TODO: restore original filenames and merge skeletons from RoIs (skeletons from boxes task) + task_type = manifest.annotation.type processor = processor_classes[task_type]( escrow_address=escrow_address, diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py new file mode 100644 index 0000000000..4edf05609f --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -0,0 +1,160 @@ +import os +from glob import glob +from typing import Dict, List, Sequence + +import datumaro as dm +import numpy as np +from defusedxml import ElementTree as ET + + +def flatten_points(input_points: Sequence[dm.Points]) -> List[dm.Points]: + results = [] + + for pts in input_points: + for point_idx in range(len(pts.points) // 2): + point_x = pts.points[2 * point_idx + 0] + point_y = pts.points[2 * point_idx + 1] + results.append(dm.Points([point_x, point_y], label=pts.label)) + + return results + + +def prepare_cvat_annotations_for_dm(dataset_root: str): + """ + Fixes project/job annotations from CVAT exported in the CVAT format + to make them readable by Datumaro. + + Datumaro doesn't support the 'meta/project' and 'meta/job' keys, + but does support 'meta/task'. The key formats are the same, + only the name is different. The key is needed to parse labels correctly. + """ + + for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): + with open(annotation_filename, "rb+") as f: + doc = ET.parse(f) + doc_root = doc.getroot() + + if doc_root.find("meta/project"): + # put labels into each task, if needed + # datumaro doesn't support /meta/project/ tag, but works with tasks, + # which is nested in the meta/project/ + labels_element = doc_root.find("meta/project/labels") + if not labels_element: + continue + + for task_element in doc_root.iterfind("meta/project/tasks/task"): + task_element.append(labels_element) + elif job_meta := doc_root.find("meta/job"): + # just rename the job into task for the same reasons + job_meta.tag = "task" + else: + continue + + f.seek(0) + f.truncate() + doc.write(f, encoding="utf-8") + + +def convert_point_arrays_dataset_to_1_point_skeletons( + dataset: dm.Dataset, labels: Sequence[str] +) -> dm.Dataset: + """ + In the COCO Person Keypoints format, we can only represent points inside skeletons. + The function converts annotations from points to skeletons in the dataset. + """ + + def _get_skeleton_label(original_label: str) -> str: + return original_label + "_sk" + + new_label_cat = dm.LabelCategories.from_iterable( + [_get_skeleton_label(label) for label in labels] + + [(label, _get_skeleton_label(label)) for label in labels] + ) + new_points_cat = dm.PointsCategories.from_iterable( + (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels + ) + converted_dataset = dm.Dataset( + categories={ + dm.AnnotationType.label: new_label_cat, + dm.AnnotationType.points: new_points_cat, + }, + media_type=dm.Image, + ) + + label_id_map: Dict[int, int] = { + original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] + for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) + } # old id -> new id + + for sample in dataset: + points = [a for a in sample.annotations if isinstance(a, dm.Points)] + points = flatten_points(points) + + skeletons = [ + dm.Skeleton( + [p.wrap(label=label_id_map[p.label])], + label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], + ) + for p in points + ] + + converted_dataset.put(sample.wrap(annotations=skeletons)) + + return converted_dataset + + +def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]) -> dm.Dataset: + """ + Removes unknown images from the dataset inplace. + + On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, + with a suffix. We don't need these frames in the resulting dataset, + and we can safely remove them. + """ + if not isinstance(known_frames, set): + known_frames = set(known_frames) + + for sample in list(dataset): + item_image_filename = sample.media.path + + if item_image_filename not in known_frames: + dataset.remove(sample.id, sample.subset) + + return dataset + + +def shift_ann( + ann: dm.Annotation, offset_x: float, offset_y: float, *, img_w: int, img_h: int +) -> dm.Annotation: + "Shift annotation coordinates with clipping to the image size" + + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann From f1ea9f9c613c89eee247cc3238b2a16a3fd3b8a9 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 29 Jan 2024 20:02:45 +0200 Subject: [PATCH 09/82] Implement boxes from points validation --- .../cvat/exchange-oracle/src/chain/escrow.py | 1 + .../src/core/annotation_meta.py | 2 +- .../src/handlers/job_export.py | 9 +- .../cvat/recording-oracle/src/chain/escrow.py | 46 ++- .../recording-oracle/src/chain/kvstore.py | 4 +- .../src/core/annotation_meta.py | 2 +- .../cvat/recording-oracle/src/core/storage.py | 2 +- .../src/core/tasks/__init__.py | 0 .../src/core/tasks/boxes_from_points.py | 92 +++++ .../crons/process_exchange_oracle_webhooks.py | 158 +------- .../handlers/process_intermediate_results.py | 345 +++++++++++++++--- .../src/handlers/validation.py | 235 ++++++++++++ .../recording-oracle/src/schemas/webhook.py | 7 +- .../recording-oracle/src/utils/annotations.py | 39 ++ .../recording-oracle/src/utils/logging.py | 6 + .../src/validators/signature.py | 3 + 16 files changed, 735 insertions(+), 216 deletions(-) create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py create mode 100644 packages/examples/cvat/recording-oracle/src/handlers/validation.py create mode 100644 packages/examples/cvat/recording-oracle/src/utils/annotations.py diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index b9593d8b73..e0061aa4cc 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -10,6 +10,7 @@ def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: + # TODO: remove mock if escrow_address.startswith("test-"): from human_protocol_sdk.constants import ChainId diff --git a/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py b/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py index 1337c5756e..e19a77efd5 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py +++ b/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -ANNOTATION_METAFILE_NAME = "annotation_meta.json" +ANNOTATION_RESULTS_METAFILE_NAME = "annotation_meta.json" RESULTING_ANNOTATIONS_FILE = "resulting_annotations.zip" diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 706a6c9b00..ab0f87ead7 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -10,7 +10,7 @@ import src.core.tasks.boxes_from_points as boxes_from_points_task import src.utils.annotations as annotation_utils -from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta +from src.core.annotation_meta import ANNOTATION_RESULTS_METAFILE_NAME, AnnotationMeta, JobMeta from src.core.config import Config from src.core.manifest import TaskManifest from src.core.storage import compose_data_bucket_filename @@ -59,7 +59,7 @@ def prepare_annotation_metafile( ] ) - return FileDescriptor(ANNOTATION_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) + return FileDescriptor(ANNOTATION_RESULTS_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) class _TaskProcessor: @@ -177,7 +177,7 @@ def __init__(self, *args, **kwargs): roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} - self.roi_name_to_roi_id: Dict[str, boxes_from_points_task.RoiInfo] = { + self.roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] for roi_id, roi_filename in roi_filenames.items() } @@ -235,7 +235,7 @@ def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: ) for roi_sample in point_roi_dataset: - roi_info = self.roi_name_to_roi_id[os.path.basename(roi_sample.id)] + roi_info = self.roi_name_to_roi_info[os.path.basename(roi_sample.id)] original_sample = self.original_key_to_sample[roi_info.original_image_key] merged_sample = merged_sample_dataset.get(original_sample.id) @@ -249,6 +249,7 @@ def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: skeleton for skeleton in original_sample.annotations if skeleton.id == roi_info.point_id + if isinstance(skeleton, dm.Skeleton) ).elements[0] old_x, old_y = old_point.points[:2] diff --git a/packages/examples/cvat/recording-oracle/src/chain/escrow.py b/packages/examples/cvat/recording-oracle/src/chain/escrow.py index 32827e3f0e..7dd103d863 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/recording-oracle/src/chain/escrow.py @@ -1,3 +1,4 @@ +import datetime import json from typing import List @@ -6,9 +7,30 @@ from human_protocol_sdk.storage import StorageClient from src.chain.web3 import get_web3 +from src.services.cloud.utils import parse_bucket_url def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: + # TODO: remove mock + if escrow_address.startswith("test-"): + from human_protocol_sdk.constants import ChainId + + return EscrowData( + chain_id=ChainId(chain_id), + id="test", + address=escrow_address, + amount_paid=10, + balance=10, + count=1, + factory_address="", + launcher="", + status="Pending", + token="HMT", + total_funded_amount=10, + created_at=datetime.datetime(2023, 1, 1), + manifest_url="http://127.0.0.1:9010/manifests/manifest_boxes_from_points_local.json", + ) + escrow = EscrowUtils.get_escrow(chain_id, escrow_address.lower()) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") @@ -42,11 +64,29 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - manifest_content = StorageClient.download_file_from_url(escrow.manifest_url) + + parsed_url = parse_bucket_url(escrow.manifest_url) + + secure = False + if parsed_url.host_url.startswith("https://"): + host = parsed_url.host_url[len("https://") :] + secure = True + elif parsed_url.host_url.startswith("http://"): + host = parsed_url.host_url[len("http://") :] + else: + host = parsed_url.host_url + + manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( + [parsed_url.path], bucket=parsed_url.bucket_name + )[0] return json.loads(manifest_content.decode("utf-8")) def store_results(chain_id: int, escrow_address: str, url: str, hash: str) -> None: + # TODO: remove mock + if escrow_address.startswith("test-"): + return + web3 = get_web3(chain_id) escrow_client = EscrowClient(web3) @@ -54,8 +94,8 @@ def store_results(chain_id: int, escrow_address: str, url: str, hash: str) -> No def get_reputation_oracle_address(chain_id: int, escrow_address: str) -> str: - return get_escrow(chain_id, escrow_address).reputationOracle + return get_escrow(chain_id, escrow_address).reputation_oracle def get_exchange_oracle_address(chain_id: int, escrow_address: str) -> str: - return get_escrow(chain_id, escrow_address).exchangeOracle + return get_escrow(chain_id, escrow_address).exchange_oracle diff --git a/packages/examples/cvat/recording-oracle/src/chain/kvstore.py b/packages/examples/cvat/recording-oracle/src/chain/kvstore.py index 50481bdd05..8b210d17b8 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/kvstore.py +++ b/packages/examples/cvat/recording-oracle/src/chain/kvstore.py @@ -23,7 +23,7 @@ def get_exchange_oracle_url(chain_id: int, escrow_address: str) -> str: web3 = get_web3(chain_id) staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.exchangeOracle)["webhook_url"] + return staking_client.get_leader(escrow.exchange_oracle)["webhook_url"] def get_reputation_oracle_url(chain_id: int, escrow_address: str) -> str: @@ -34,4 +34,4 @@ def get_reputation_oracle_url(chain_id: int, escrow_address: str) -> str: web3 = get_web3(chain_id) staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.reputationOracle)["webhook_url"] + return staking_client.get_leader(escrow.reputation_oracle)["webhook_url"] diff --git a/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py b/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py index 1337c5756e..e19a77efd5 100644 --- a/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py +++ b/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -ANNOTATION_METAFILE_NAME = "annotation_meta.json" +ANNOTATION_RESULTS_METAFILE_NAME = "annotation_meta.json" RESULTING_ANNOTATIONS_FILE = "resulting_annotations.zip" diff --git a/packages/examples/cvat/recording-oracle/src/core/storage.py b/packages/examples/cvat/recording-oracle/src/core/storage.py index b934b865c0..6c5c53dbe2 100644 --- a/packages/examples/cvat/recording-oracle/src/core/storage.py +++ b/packages/examples/cvat/recording-oracle/src/core/storage.py @@ -7,4 +7,4 @@ def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filena def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: - return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" + return f"{escrow_address}@{chain_id}{Config.exchange_oracle_storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py b/packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py new file mode 100644 index 0000000000..c9320473b6 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +BboxPointMapping = Dict[int, int] + + +@frozen +class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return (Path(gt_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_bbox_point_mapping(self, bbox_point_mapping: BboxPointMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping(self, bbox_point_mapping_data: bytes) -> BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py index da8b0227e5..aef5ebb812 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py @@ -1,40 +1,18 @@ -import io import logging -import os from typing import Dict import httpx from sqlalchemy.orm import Session -import src.chain.escrow as escrow -import src.core.annotation_meta as annotation -import src.core.validation_meta as validation -import src.services.cloud.client as cloud_client import src.services.webhook as oracle_db_service from src.chain.kvstore import get_exchange_oracle_url from src.core.config import Config -from src.core.oracle_events import ( - RecordingOracleEvent_TaskCompleted, - RecordingOracleEvent_TaskRejected, -) from src.core.types import ExchangeOracleEventType, OracleWebhookTypes from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.process_intermediate_results import ( - ValidationSuccess, - parse_annotation_metafile, - process_intermediate_results, - serialize_validation_meta, -) +from src.handlers.validation import validate_results from src.log import ROOT_LOGGER_NAME from src.models.webhook import Webhook -from src.services.cloud import download_file -from src.services.cloud.utils import parse_bucket_url -from src.utils.assignments import ( - compose_results_bucket_filename, - compute_resulting_annotations_hash, - parse_manifest, -) from src.utils.logging import get_function_logger from src.utils.webhooks import prepare_outgoing_webhook_body, prepare_signed_message @@ -90,142 +68,12 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge "Validating the results" ) - escrow.validate_escrow(webhook.chain_id, webhook.escrow_address) - - manifest = parse_manifest( - escrow.get_escrow_manifest(webhook.chain_id, webhook.escrow_address) - ) - - excor_bucket_host = Config.exchange_oracle_storage_config.provider_endpoint_url() - excor_bucket_name = Config.exchange_oracle_storage_config.data_bucket_name - - excor_annotation_meta_path = compose_results_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - annotation.ANNOTATION_METAFILE_NAME, - ) - annotation_metafile_data = download_file( - excor_bucket_host, excor_bucket_name, excor_annotation_meta_path - ) - annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) - - job_annotations: Dict[int, bytes] = {} - for job_meta in annotation_meta.jobs: - job_filename = compose_results_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - job_meta.annotation_filename, - ) - job_annotations[job_meta.job_id] = download_file( - excor_bucket_host, excor_bucket_name, job_filename - ) - - parsed_gt_bucket_url = parse_bucket_url(manifest.validation.gt_url) - gt_bucket_host = parsed_gt_bucket_url.host_url - gt_bucket_name = parsed_gt_bucket_url.bucket_name - gt_filename = parsed_gt_bucket_url.path - gt_file_data = download_file( - gt_bucket_host, - gt_bucket_name, - gt_filename, - ) - - excor_merged_annotation_path = compose_results_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - annotation.RESULTING_ANNOTATIONS_FILE, - ) - merged_annotations = download_file( - excor_bucket_host, excor_bucket_name, excor_merged_annotation_path - ) - - validation_results = process_intermediate_results( - db_session, + validate_results( escrow_address=webhook.escrow_address, chain_id=webhook.chain_id, - meta=annotation_meta, - job_annotations={k: io.BytesIO(v) for k, v in job_annotations.items()}, - merged_annotations=io.BytesIO(merged_annotations), - gt_annotations=io.BytesIO(gt_file_data), - manifest=manifest, - logger=logger, + db_session=db_session, ) - if isinstance(validation_results, ValidationSuccess): - logger.info( - f"Validation for escrow_address={webhook.escrow_address} successful, " - f"average annotation quality is {validation_results.average_quality:.2f}" - ) - - recor_merged_annotations_path = compose_results_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - validation.RESULTING_ANNOTATIONS_FILE, - ) - - recor_validation_meta_path = compose_results_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - validation.VALIDATION_METAFILE_NAME, - ) - validation_metafile = serialize_validation_meta(validation_results.validation_meta) - - storage_client = cloud_client.S3Client( - Config.storage_config.provider_endpoint_url(), - access_key=Config.storage_config.access_key, - secret_key=Config.storage_config.secret_key, - ) - - # TODO: add encryption - storage_client.create_file( - Config.storage_config.data_bucket_name, - recor_merged_annotations_path, - validation_results.resulting_annotations, - ) - storage_client.create_file( - Config.storage_config.data_bucket_name, - recor_validation_meta_path, - validation_metafile, - ) - - escrow.store_results( - webhook.chain_id, - webhook.escrow_address, - Config.storage_config.bucket_url() - + os.path.dirname(recor_merged_annotations_path), - compute_resulting_annotations_hash(validation_results.resulting_annotations), - ) - - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.reputation_oracle, - event=RecordingOracleEvent_TaskCompleted(), - ) - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.exchange_oracle, - event=RecordingOracleEvent_TaskCompleted(), - ) - else: - logger.info( - f"Validation for escrow_address={webhook.escrow_address} failed, " - f"rejected {len(validation_results.rejected_job_ids)} jobs" - ) - - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.exchange_oracle, - event=RecordingOracleEvent_TaskRejected( - rejected_job_ids=validation_results.rejected_job_ids - ), - ) - case _: assert False, f"Unknown exchange oracle event {webhook.event_type}" diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index ad004673b7..5d786b0a28 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -3,18 +3,24 @@ import os from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Type, Union +from typing import Dict, List, Optional, Sequence, Tuple, Type, Union import datumaro as dm import numpy as np from attrs import define from sqlalchemy.orm import Session +import src.core.tasks.boxes_from_points as boxes_from_points_task import src.services.validation as db_service from src.core.annotation_meta import AnnotationMeta +from src.core.config import Config from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename from src.core.types import TaskType from src.core.validation_meta import JobMeta, ResultMeta, ValidationMeta +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud.utils import BucketAccessInfo +from src.utils.annotations import shift_ann from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive from src.validation.dataset_comparison import ( BboxDatasetComparator, @@ -39,12 +45,14 @@ class ValidationFailure: TaskType.image_label_binary: "cvat_images", TaskType.image_points: "coco_person_keypoints", TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", } DM_GT_DATASET_FORMAT_MAPPING = { TaskType.image_label_binary: "cvat_images", TaskType.image_points: "coco_instances", # we compare points against boxes TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", } @@ -52,68 +60,311 @@ class ValidationFailure: # TaskType.image_label_binary: TagDatasetComparator, # TODO: implement if support is needed TaskType.image_boxes: BboxDatasetComparator, TaskType.image_points: PointsDatasetComparator, + TaskType.image_boxes_from_points: BboxDatasetComparator, } +_JobResults = Dict[int, float] +_RejectedJobs = Sequence[int] -def process_intermediate_results( - session: Session, - *, - escrow_address: str, - chain_id: int, - meta: AnnotationMeta, - job_annotations: Dict[int, io.RawIOBase], - gt_annotations: io.RawIOBase, - merged_annotations: io.RawIOBase, - manifest: TaskManifest, - logger: logging.Logger, -) -> Union[ValidationSuccess, ValidationFailure]: - # validate - task_type = manifest.annotation.type - dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - job_results: Dict[int, float] = {} - rejected_job_ids: List[int] = [] +class _TaskValidator: + def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): + self.escrow_address = escrow_address + self.chain_id = chain_id + self.manifest = manifest + + self.input_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + + self.gt_annotations: Optional[io.IOBase] = None + self.job_annotations: Optional[io.IOBase] = None + self.merged_annotations: Optional[io.IOBase] = None + + def validate(self) -> Tuple[_JobResults, _RejectedJobs]: + assert self.gt_annotations is not None + assert self.job_annotations is not None + assert self.merged_annotations is not None + + manifest = self.manifest + task_type = manifest.annotation.type + dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + + gt_annotations = self.gt_annotations + job_annotations = self.job_annotations + merged_annotations = self.merged_annotations + + job_results: Dict[int, float] = {} + rejected_job_ids: List[int] = [] + + with TemporaryDirectory() as tempdir: + tempdir = Path(tempdir) - with TemporaryDirectory() as tempdir: - tempdir = Path(tempdir) + gt_dataset_path = tempdir / "gt.json" + gt_dataset_path.write_bytes(gt_annotations.read()) + gt_dataset = dm.Dataset.import_from( + os.fspath(gt_dataset_path), format=DM_GT_DATASET_FORMAT_MAPPING[task_type] + ) + + comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( + min_similarity_threshold=manifest.validation.min_quality + ) + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), format=dataset_format + ) + + job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) + job_results[job_cvat_id] = job_mean_accuracy - gt_dataset_path = tempdir / "gt.json" - gt_dataset_path.write_bytes(gt_annotations.read()) - gt_dataset = dm.Dataset.import_from( - os.fspath(gt_dataset_path), format=DM_GT_DATASET_FORMAT_MAPPING[task_type] + if job_mean_accuracy < manifest.validation.min_quality: + rejected_job_ids.append(job_cvat_id) + + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + extract_zip_archive(merged_annotations, merged_dataset_path) + + merged_dataset = dm.Dataset.import_from( + os.fspath(merged_dataset_path), format=merged_dataset_format + ) + put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + + updated_merged_dataset_path = tempdir / "merged_updated" + merged_dataset.export( + updated_merged_dataset_path, merged_dataset_format, save_media=False + ) + + updated_merged_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) + updated_merged_dataset_archive.seek(0) + + return job_results, rejected_job_ids, updated_merged_dataset_archive + + +class _BoxesFromPointsValidator(_TaskValidator): + def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): + super().__init__(escrow_address, chain_id, manifest) + + ( + boxes_to_points_mapping, + roi_filenames, + roi_infos, + gt_dataset, + points_dataset, + ) = self._download_task_meta() + + self.gt_dataset = gt_dataset + self.points_dataset = points_dataset + + point_key_to_sample = { + skeleton.id: sample + for sample in points_dataset + for skeleton in sample.annotations + if isinstance(skeleton, dm.Skeleton) + } + + self.bbox_key_to_sample = { + bbox.id: sample + for sample in gt_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + self.point_key_to_bbox_key = {v: k for k, v in boxes_to_points_mapping.items()} + self.roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + self.roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self.roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self.point_offset_by_roi_id = {} + "Offset from new to old coords, (dx, dy)" + + for roi_info in roi_infos: + point_sample = point_key_to_sample[roi_info.point_id] + old_point = next( + skeleton + for skeleton in point_sample.annotations + if skeleton.id == roi_info.point_id + if isinstance(skeleton, dm.Skeleton) + ).elements[0] + old_x, old_y = old_point.points[:2] + offset_x = old_x - roi_info.point_x + offset_y = old_y - roi_info.point_y + self.point_offset_by_roi_id[roi_info.point_id] = (offset_x, offset_y) + + def _download_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.from_raw_url( + Config.exchange_oracle_storage_config.bucket_url() + ) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + storage_client = make_cloud_client(oracle_data_bucket) + + boxes_to_points_mapping = serializer.parse_bbox_point_mapping( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BBOX_POINT_MAPPING_FILENAME + ), + ) ) - comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) ) - for job_cvat_id, job_annotations_file in job_annotations.items(): - job_dataset_path = tempdir / str(job_cvat_id) - extract_zip_archive(job_annotations_file, job_dataset_path) + rois = serializer.parse_roi_info( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) - job_dataset = dm.Dataset.import_from(os.fspath(job_dataset_path), format=dataset_format) + gt_dataset = serializer.parse_gt_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.GT_FILENAME + ), + ) + ) - job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) - job_results[job_cvat_id] = job_mean_accuracy + points_dataset = serializer.parse_points_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINTS_FILENAME + ), + ) + ) - if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) + return boxes_to_points_mapping, roi_filenames, rois, gt_dataset, points_dataset - merged_dataset_path = tempdir / str("merged") - merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - extract_zip_archive(merged_annotations, merged_dataset_path) + def _make_gt_dataset_for_job(self, job_dataset: dm.Dataset) -> dm.Dataset: + job_gt_dataset = dm.Dataset(categories=self.gt_dataset.categories(), media_type=dm.Image) - merged_dataset = dm.Dataset.import_from( - os.fspath(merged_dataset_path), format=merged_dataset_format - ) - put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + for job_sample in job_dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + + point_bbox_key = self.point_key_to_bbox_key.get(roi_info.point_id, None) + if point_bbox_key is None: + continue # roi is not from GT set + + bbox_sample = self.bbox_key_to_sample[point_bbox_key] + + bbox = next(bbox for bbox in bbox_sample.annotations if bbox.id == point_bbox_key) + roi_shift_x, roi_shift_y = self.point_offset_by_roi_id[roi_info.point_id] + + bbox_in_roi_coords = shift_ann( + bbox, + offset_x=-roi_shift_x, + offset_y=-roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + job_gt_dataset.put(job_sample.wrap(annotations=[bbox_in_roi_coords])) + + return job_gt_dataset + + def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: + assert self.job_annotations is not None + assert self.merged_annotations is not None + + manifest = self.manifest + task_type = manifest.annotation.type + dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + + job_annotations = self.job_annotations + merged_annotations = self.merged_annotations - updated_merged_dataset_path = tempdir / str("merged_updated") - merged_dataset.export(updated_merged_dataset_path, merged_dataset_format, save_media=False) + job_results: Dict[int, float] = {} + rejected_job_ids: List[int] = [] - updated_merged_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) - updated_merged_dataset_archive.seek(0) + with TemporaryDirectory() as tempdir: + tempdir = Path(tempdir) + + comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( + min_similarity_threshold=manifest.validation.min_quality + ) + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), format=dataset_format + ) + job_gt_dataset = self._make_gt_dataset_for_job(job_dataset) + + job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) + job_results[job_cvat_id] = job_mean_accuracy + + if job_mean_accuracy < manifest.validation.min_quality: + rejected_job_ids.append(job_cvat_id) + + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + extract_zip_archive(merged_annotations, merged_dataset_path) + + merged_dataset = dm.Dataset.import_from( + os.fspath(merged_dataset_path), format=merged_dataset_format + ) + put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) + + updated_merged_dataset_path = tempdir / "merged_updated" + merged_dataset.export( + updated_merged_dataset_path, merged_dataset_format, save_media=False + ) + + updated_merged_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) + updated_merged_dataset_archive.seek(0) + + return job_results, rejected_job_ids, updated_merged_dataset_archive + + +def process_intermediate_results( + session: Session, + *, + escrow_address: str, + chain_id: int, + meta: AnnotationMeta, + job_annotations: Dict[int, io.RawIOBase], + gt_annotations: io.RawIOBase, + merged_annotations: io.RawIOBase, + manifest: TaskManifest, + logger: logging.Logger, +) -> Union[ValidationSuccess, ValidationFailure]: + # validate + task_type = manifest.annotation.type + if task_type in [TaskType.image_label_binary, TaskType.image_boxes, TaskType.image_points]: + validator_type = _TaskValidator + elif task_type == TaskType.image_boxes_from_points: + validator_type = _BoxesFromPointsValidator + else: + raise Exception(f"Unknown task type {task_type}") + + validator = validator_type(escrow_address=escrow_address, chain_id=chain_id, manifest=manifest) + validator.gt_annotations = gt_annotations + validator.job_annotations = job_annotations + validator.merged_annotations = merged_annotations + job_results, rejected_job_ids, updated_merged_dataset_archive = validator.validate() if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -212,6 +463,8 @@ def put_gt_into_merged_dataset( merged_dataset.put(sample.wrap(annotations=annotations)) case TaskType.image_label_binary.value: merged_dataset.update(gt_dataset) + case TaskType.image_boxes_from_points: + merged_dataset.update(gt_dataset) case _: assert False, f"Unknown task type {manifest.annotation.type}" diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py new file mode 100644 index 0000000000..ec9030f8ff --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -0,0 +1,235 @@ +import io +import os +from logging import Logger +from typing import Dict, Optional, Union + +from sqlalchemy.orm import Session + +import src.chain.escrow as escrow +import src.core.annotation_meta as annotation +import src.core.validation_meta as validation +import src.services.webhook as oracle_db_service +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.oracle_events import ( + RecordingOracleEvent_TaskCompleted, + RecordingOracleEvent_TaskRejected, +) +from src.core.storage import ( + compose_results_bucket_filename as compose_annotation_results_bucket_filename, +) +from src.core.types import OracleWebhookTypes +from src.handlers.process_intermediate_results import ( + ValidationSuccess, + parse_annotation_metafile, + process_intermediate_results, + serialize_validation_meta, +) +from src.log import ROOT_LOGGER_NAME +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud import s3 +from src.services.cloud.utils import BucketAccessInfo +from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest +from src.utils.logging import NullLogger, get_function_logger +from validators import ValidationFailure + +module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" + + +class _TaskValidator: + def __init__( + self, escrow_address: str, chain_id: int, manifest: TaskManifest, db_session: Session + ) -> None: + self.escrow_address = escrow_address + self.chain_id = chain_id + self.manifest = manifest + self.db_session = db_session + self.logger: Logger = NullLogger() + + self.data_bucket = BucketAccessInfo.from_raw_url( + Config.exchange_oracle_storage_config.bucket_url() + ) + + self.annotation_meta: Optional[annotation.AnnotationMeta] = None + self.job_annotations: Optional[Dict[int, bytes]] = None + self.merged_annotations: Optional[bytes] = None + self.gt_data: Optional[bytes] = None + + def set_logger(self, logger: Logger): + self.logger = logger + + def _download_results_meta(self): + data_bucket_client = make_cloud_client(self.data_bucket) + + annotation_meta_path = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + annotation.ANNOTATION_RESULTS_METAFILE_NAME, + ) + annotation_metafile_data = data_bucket_client.download_file( + self.data_bucket.url.bucket_name, annotation_meta_path + ) + self.annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) + + def _download_annotations(self): + assert self.annotation_meta is not None + + data_bucket_client = make_cloud_client(self.data_bucket) + + job_annotations = {} + for job_meta in self.annotation_meta.jobs: + job_filename = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + job_meta.annotation_filename, + ) + job_annotations[job_meta.job_id] = data_bucket_client.download_file( + self.data_bucket.url.bucket_name, job_filename + ) + + excor_merged_annotation_path = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + annotation.RESULTING_ANNOTATIONS_FILE, + ) + merged_annotations = data_bucket_client.download_file( + self.data_bucket.url.bucket_name, excor_merged_annotation_path + ) + + self.job_annotations = job_annotations + self.merged_annotations = merged_annotations + + def _download_gt(self): + gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + gt_bucket_client = make_cloud_client(gt_bucket) + self.gt_data = gt_bucket_client.download_file(gt_bucket.url.bucket_name, gt_bucket.url.path) + + def _download_results(self): + self._download_results_meta() + self._download_annotations() + self._download_gt() + + ValidationResult = Union[ValidationSuccess, ValidationFailure] + + def _process_annotation_results(self) -> ValidationResult: + assert self.annotation_meta is not None + assert self.job_annotations is not None + assert self.merged_annotations is not None + assert self.gt_data is not None + + # TODO: refactor further + return process_intermediate_results( + session=self.db_session, + escrow_address=self.escrow_address, + chain_id=self.chain_id, + meta=self.annotation_meta, + job_annotations={k: io.BytesIO(v) for k, v in self.job_annotations.items()}, + merged_annotations=io.BytesIO(self.merged_annotations), + gt_annotations=io.BytesIO(self.gt_data), + manifest=self.manifest, + logger=self.logger, + ) + + def validate(self): + self._download_results() + + validation_result = self._process_annotation_results() + + self._handle_validation_result(validation_result) + + def _compose_validation_results_bucket_filename(self, filename: str) -> str: + return f"{self.escrow_address}@{self.chain_id}/{filename}" + + def _handle_validation_result(self, validation_result: ValidationResult): + logger = self.logger + escrow_address = self.escrow_address + chain_id = self.chain_id + db_session = self.db_session + + if isinstance(validation_result, ValidationSuccess): + logger.info( + f"Validation for escrow_address={escrow_address} successful, " + f"average annotation quality is {validation_result.average_quality:.2f}" + ) + + recor_merged_annotations_path = self._compose_validation_results_bucket_filename( + validation.RESULTING_ANNOTATIONS_FILE, + ) + + recor_validation_meta_path = self._compose_validation_results_bucket_filename( + validation.VALIDATION_METAFILE_NAME, + ) + validation_metafile = serialize_validation_meta(validation_result.validation_meta) + + storage_client = s3.S3Client( + Config.storage_config.provider_endpoint_url(), + access_key=Config.storage_config.access_key, + secret_key=Config.storage_config.secret_key, + ) + + # TODO: add encryption + storage_client.create_file( + Config.storage_config.data_bucket_name, + recor_merged_annotations_path, + validation_result.resulting_annotations, + ) + storage_client.create_file( + Config.storage_config.data_bucket_name, + recor_validation_meta_path, + validation_metafile, + ) + + escrow.store_results( + chain_id, + escrow_address, + Config.storage_config.bucket_url() + os.path.dirname(recor_merged_annotations_path), + compute_resulting_annotations_hash(validation_result.resulting_annotations), + ) + + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.reputation_oracle, + event=RecordingOracleEvent_TaskCompleted(), + ) + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.exchange_oracle, + event=RecordingOracleEvent_TaskCompleted(), + ) + else: + logger.info( + f"Validation for escrow_address={escrow_address} failed, " + f"rejected {len(validation_result.rejected_job_ids)} jobs" + ) + + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.exchange_oracle, + event=RecordingOracleEvent_TaskRejected( + rejected_job_ids=validation_result.rejected_job_ids + ), + ) + + +def validate_results( + escrow_address: str, + chain_id: int, + db_session: Session, +): + logger = get_function_logger(module_logger_name) + + escrow.validate_escrow(chain_id=chain_id, escrow_address=escrow_address) + + manifest = parse_manifest(escrow.get_escrow_manifest(chain_id, escrow_address)) + + validator = _TaskValidator( + escrow_address=escrow_address, chain_id=chain_id, manifest=manifest, db_session=db_session + ) + validator.set_logger(logger) + validator.validate() diff --git a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py index e47a129847..78dcb56586 100644 --- a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py @@ -14,9 +14,10 @@ class OracleWebhook(BaseModel): event_data: Optional[dict] = None timestamp: Optional[datetime] = None # TODO: remove optional - @validator("escrow_address", allow_reuse=True) - def validate_escrow_(cls, value): - return validate_address(value) + # TODO: restore + # @validator("escrow_address", allow_reuse=True) + # def validate_escrow_(cls, value): + # return validate_address(value) # pylint: disable=too-few-public-methods class Config: diff --git a/packages/examples/cvat/recording-oracle/src/utils/annotations.py b/packages/examples/cvat/recording-oracle/src/utils/annotations.py new file mode 100644 index 0000000000..5a62b80fb4 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/utils/annotations.py @@ -0,0 +1,39 @@ +import datumaro as dm +import numpy as np + + +def shift_ann( + ann: dm.Annotation, offset_x: float, offset_y: float, *, img_w: int, img_h: int +) -> dm.Annotation: + "Shift annotation coordinates with clipping to the image size" + + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann diff --git a/packages/examples/cvat/recording-oracle/src/utils/logging.py b/packages/examples/cvat/recording-oracle/src/utils/logging.py index d9ce28d024..146f6c219b 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/logging.py +++ b/packages/examples/cvat/recording-oracle/src/utils/logging.py @@ -20,3 +20,9 @@ def get_function_logger( function_name = current_function_name(depth=2) return parent_logger.getChild(function_name) + + +class NullLogger(logging.Logger): + def __init__(self, name: str = "", level=0) -> None: + super().__init__(name, level) + self.disabled = True diff --git a/packages/examples/cvat/recording-oracle/src/validators/signature.py b/packages/examples/cvat/recording-oracle/src/validators/signature.py index deb3364a70..b63cc683e1 100644 --- a/packages/examples/cvat/recording-oracle/src/validators/signature.py +++ b/packages/examples/cvat/recording-oracle/src/validators/signature.py @@ -22,6 +22,9 @@ async def validate_oracle_webhook_signature( OracleWebhookTypes.exchange_oracle: exchange_oracle_address, } + # TODO: remove mock + return OracleWebhookTypes.exchange_oracle + matched_signer = next( ( s_type From 0dda9894ffebc5379ffa71d3a4fee57552dae23d Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 30 Jan 2024 16:35:32 +0200 Subject: [PATCH 10/82] Integrate SDK updates --- .../examples/cvat/exchange-oracle/poetry.lock | 4109 ++++++++-------- .../cvat/exchange-oracle/pyproject.toml | 2 +- .../cvat/exchange-oracle/src/chain/escrow.py | 25 +- .../cvat/exchange-oracle/src/chain/kvstore.py | 12 +- .../cvat/recording-oracle/poetry.lock | 4188 ++++++++--------- .../cvat/recording-oracle/pyproject.toml | 2 +- .../cvat/recording-oracle/src/chain/escrow.py | 26 +- .../recording-oracle/src/chain/kvstore.py | 11 +- 8 files changed, 4086 insertions(+), 4289 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/poetry.lock b/packages/examples/cvat/exchange-oracle/poetry.lock index e2875d7008..5a13603635 100644 --- a/packages/examples/cvat/exchange-oracle/poetry.lock +++ b/packages/examples/cvat/exchange-oracle/poetry.lock @@ -1,119 +1,105 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" -version = "3.8.6" +version = "3.9.3" description = "Async http client/server framework (asyncio)" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -126,14 +112,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alembic" -version = "1.12.0" +version = "1.13.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "alembic-1.12.0-py3-none-any.whl", hash = "sha256:03226222f1cf943deee6c85d9464261a6c710cd19b4fe867a3ad1f25afda610f"}, - {file = "alembic-1.12.0.tar.gz", hash = "sha256:8e7645c32e4f200675e69f0745415335eb59a3663f5feb487abfa0b30c45888b"}, + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, ] [package.dependencies] @@ -142,35 +127,34 @@ SQLAlchemy = ">=1.3.0" typing-extensions = ">=4" [package.extras] -tz = ["python-dateutil"] +tz = ["backports.zoneinfo"] [[package]] name = "anyio" -version = "4.0.0" +version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, - {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "apscheduler" version = "3.10.4" description = "In-process task scheduler with Cron-like capabilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -181,7 +165,7 @@ files = [ [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -195,11 +179,67 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + [[package]] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -209,185 +249,183 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "bitarray" -version = "2.8.2" +version = "2.9.2" description = "efficient arrays of booleans -- C extension" -category = "main" optional = false python-versions = "*" files = [ - {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525eda30469522cd840a11ba866d0616c132f6c4be8966a297d7545e97fcb822"}, - {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3d9730341c825eb167ca06c9dddf6ad4d1b4e71ea7da73cc8c5139fcb5e14ca"}, - {file = "bitarray-2.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad8f8c39c8df184e346184699783f105755003662f0dbe1233d9d9849650ab5f"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8d08330d250df47088c13683322083afbdfafdc31df205616506d6b9f068f"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f19ccba8a6ddf1382b0fb4fb8d4e1330e4a1b148e5d198f0981ba2a97c3492"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4db2e0f58153a376d9a14873e342d507ca32640640284cddf3c1e74a65929477"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b3c27aeea1752f0c1df1e29115e4b6f0249173d71e53c5f7e2c821706f028b"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef23f62b3abd287cf368341540ef2a81c86b48de9d488e182e63fe24ac165538"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6d79fd3c58a4dc71ffd0fc55982a9a2079fe94c76ccff2777092f6107d6a049a"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8528c59d3d3df6618777892b60435022d8917de9ea32933d439c7ffd24437237"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c35bb5fe018fd9c42be3c28e74dc7dcfae471c3c6689679dbd0bd1d6dc0f51b7"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:232e8faa8e624f3eb0552a636ebe745cee00480e0e56ad62f17808d281838f2e"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:945e97ad2bbf7885426f39641a735a31fd4ca2e84e4d0cd271d9880372d6eae1"}, - {file = "bitarray-2.8.2-cp310-cp310-win32.whl", hash = "sha256:88c2d427ab1b20f220c1d53171b0691faa8f0a219367d84e859f1001e90ceefc"}, - {file = "bitarray-2.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:f7c5745e0f96c2c16c03c7540dbe26f3b62ddee63059be0a014156933f054024"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a610426251d1340baa4d8b7942d2cbfe6a1e20b92c66817ab582e0d341185ab5"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599b04b04eb1b5b964a35986bea2bc4381145836fe550cc33c40a796b855b985"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9014660472f2080d550157164cc5f9376245a34a0ab877b82b95c1f894af5b28"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:532d63c54159f7e0fb520e2f72ef596493bc43810eaa75fac7a188e898ab593b"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1563f11dd70cb1684cfe841e4cf7f35d4f65769de21d12b72cf773a7932615"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e456150af62ee1f24a0c9976947629bfb80d80b4fbd37aa901cf794db6ba9b0"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc29909e4cef05d5e49f5d77ace1dc49311c7791734a048b690521c76b4b7a0"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:608385f07a4b0391d4982d1efb83ad70920cd8ca495a7868e44d2a4511cbf84e"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2baf7ec353fa64917045b3efe26e7c12ce0d7b4d120c3773a612dce54f91585"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2c39d1cb04fc277701de6fe2119cc71facc4aff2ca0414b2e326aec337fa1ab4"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:3caf4ca668854bb23db4b65af0068238677b5791bcc45694bf8990f3e26e85c9"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4bbfe4474d3470c724e283bd1fe8ee9ab3cb6a4c378112926f45d41e326a7622"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb941981676dc7859d53199a10a33ca56a3146cce6a45bc6ad70572c1147157d"}, - {file = "bitarray-2.8.2-cp311-cp311-win32.whl", hash = "sha256:e8963d7ac292f41654fa7cbc1a34efdb09e5a42399b2e3689c3fd5b8b4e0fe16"}, - {file = "bitarray-2.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:ee779a291330287b341044635fce2979176d113b0dcce0308dc5d62da7951eec"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:05d84765bbfd0aa10890c765c56c917c237987325c4e327f3c0febbfc34365c8"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c7b7be4bff27d7ce6a81d3993755371b5f5b42436afa151868e8fd599acbab19"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c3d51ab9f3d5b9a10295abe480c50bf74ee5bf3d984c4cee77e493e575acc869"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00bad63ef6f9d22ba36b01b89167176a451ea22a916d1dfa77d73e0298f1d1f9"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:225e19d37b234d4d721557434b7d5590cd63b6342492b689e2d694d44d7cc537"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e3ab9870c496e5a058436bf4d96ed111ca6154c8ef8147b70c44c188d6fb2c"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3e182c766cd6f302e99e0d8e44927d533356e9d6ac93fcd09987ebead467aa"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7bb559b68eb9cb3c4f867eb9fb39a696c4da70a41fad37b410bd0c7b426a8ce"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:97e658a3793478d6bca684f47f29f62542312683687bc045dc3cb588160e74b3"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:dd351b8fbc77c2e2ebc3eeadc0cf72bd5024a43bef5a847697e2b076d1201636"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:280809e56a7098f48165ce134222098e4cfe7084b10d69bbc31367942e541dfd"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14bc38ced7edffff25ee748c1eabc530624c9af68f86322b030b11b7918b966f"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de4953b6b1e19dabd23767bd1f83f1cf73978372189dec0e2dd8b3d6971100d6"}, - {file = "bitarray-2.8.2-cp312-cp312-win32.whl", hash = "sha256:99196b4730d887a4bc578f05039b55dc57b131c81b5a5e03efa619b587bdf293"}, - {file = "bitarray-2.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:215a5bf8fdcbed700cc8782d4044e1f036606d5c321710d83e8da6d0fdfe07d5"}, - {file = "bitarray-2.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9c54136c9fab2cefe9801e336b8a3aa7299bcfe7f387379cc6394ad1d5a484b"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08ad70c1555d9622cecd8f1b132a5341d183a9161aba93cc9739bbaabe4220b0"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:384be6b7df8fb6a93ddd88d4184094f2ba4f1d07c30dcd4ae164d185d31a2af6"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd2a098250c683d248a6490ac437ed56f7164d2151572231bd26c76bfe111b11"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ae5c18b9a70cb0ae576a8a3c8a9a0659356c016b49cc6b263dd987d344f30d"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:188f5780f1cfbeba0c3ddb1aa3fa0415ab1a8aa04e9e89f70ad5403197013437"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5f2a96c5b40727bc21a695d3a106f49e88572fa11427bf2193cabd99e624c901"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b6df948da34b5fb949698092573d798c76c54f2f2188db59276d599075f9ed04"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f00c328b8dae1828844bac019dfe425d10a2043cc70e2f967224c5392d19ad"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7965108069f9731306a882872c23ad4f5a8531668e82b27932a19814c52a8dd8"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:420aa610fe392c4ee700e474673276bb4f3c4f091d001f58b1f018bf650840c1"}, - {file = "bitarray-2.8.2-cp36-cp36m-win32.whl", hash = "sha256:b85929db81105c06e8292c05cac093068e86464555c628c03f99c9f8090d68d4"}, - {file = "bitarray-2.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:cba09dfd3aea2addc994eb21a861c3cea2d68141bb7ebe68b0e94c73405540f9"}, - {file = "bitarray-2.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:172169099797f1ec469b0aadb00c653193a74757f99312c9c17dc1a18d23d972"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a4fed240728dcc96966e0c4cfd3dce870525377a1cb5afac8e5cfe116ff7b"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff31bef13fd278446b6d1969a46db9f02c36fd905f3e75878f0fe17271f7d897"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb8b727cd9ddff848c5f73e65470abb110f026beab403bcebbd74e7439b9bd8f"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1356c86eefbde3fe8a3c39fb81bbc8b16acc8e442e191408042e8b1d6904e3"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7706336bd15acf4e42300579e42bef742c01a4eb202998f6c20c443a2ce5fd60"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a4b43949477dc2b0d3e1d8b7c413ed74f515cef01954cdcc3fb1e2dcc49f2aff"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:06d9de5db244c6e45a5318713367765de0a57d82ad616869a004a710a95541e9"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5569c8314335e92570c471d60b4b03eb2a4467864805a560d133d24b27b3961a"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:76a4faef4c31953aa7b9ebe00d162f7ce9bc03fc8d423ab2dc690a11d7520a8e"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1474db8c4297026e1daa1699e70e25e56dff91104fe025b1a9804332f2737604"}, - {file = "bitarray-2.8.2-cp37-cp37m-win32.whl", hash = "sha256:85b504f233f0484e9a74df4f286a9ae56fbbe2a648c45726761cf7b6f072cdc8"}, - {file = "bitarray-2.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3dde123ce85d1ba99d9bdf44b1b3174fa22bc8fb10004e0d72bb661a0444c1a9"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23fae6a5a1403d16592b8823d5dea93f738c6e217a1e1bb0eefad242fb03d47f"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c44b3022115eb1697315bc51aeadbade1a19d7188bcda66c52d91209cf2963ca"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fea9354b7169810e2bdd6f3265ff128b564a25d38479b9ad0a9c5776e4fd0cfc"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f699bf2cb223aeec04a106003bd2bf8a4fc6d4c5eddf79cacecb6b267657ac5"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:462c9425fbc5315cbc20a72ca62558e5545bb0f6dc9355e2fa96fa747e9b1a80"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c8716b4c45fb128cd4da143749e276f150ecb0acb711f4969d7e7ebc9b2a675"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79fde5b27e35aedd958f5fb58ebabce47d7eddae5a5e3774088c30c9610195ef"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abf2593b91e36f1cb1c40ac895993c7d2eb30d3f1cb0954a80e5f13697b6b69"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ab2e03dd140ab93b91f94a785d1cd6082d5ab53ab6ec958726efa0ad17f7b87a"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9e895cc3e5ffee269dd9866097e227a68022ef2b78d627a6ed737534d0c88c14"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0bbeb7120ec1a9b26ce423e74cad7b414cea9e35f8e05599e3b3dceb87f4d1b6"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51d45d56be14b69720d11a8c61e101d86a65dc8a3a9f356bbe4d98cf4f3c5617"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:726a598e34657772e5f131115741ea8709e9b55fa35d63c4717bc16b2a737d38"}, - {file = "bitarray-2.8.2-cp38-cp38-win32.whl", hash = "sha256:ab87c4c50d65932788d058adbbd28a209144523ffacbab81dd41582ffce26af9"}, - {file = "bitarray-2.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:316147fb62c810a7667277e5ae7bb75b2871c32d2c398aeb4503cbd4cf3315e7"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36bdde1aba78e4a3a6ce5cbebd0a6bc967b0c3fbd8bd99a197dcc17d654f423c"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:932f7b77750dff7140522dc97dfd94533a599ef1c5d0be3733f556fd44a68821"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5819b95d0ccce864066f062d2329363ae8a64b9c3d076d039c75ffc9204c2a12"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c28b52e59a5e6aa00a929b35b04473bd479a74237ab1170c573c49e8aca61fe"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ecdd528268478efeb78ed0132b01104bda6cd8f10c8a57708fc87b1add77e4d"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f6f245d4a5e707d48274f38551b654a36db4fb83437c98be00d2019263aa364"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b088f06d9e2f523683ae363e227173ac454dbb56c938c6d42791fdd78bad8da7"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e883919cea8e446c5c49717a7ce5c93a016a02b9429b81d64b9ab1d80fc12e42"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:09d729420b8edc4d8a23a518ae4553074a0054d0441c1a461b425c2f033fab5e"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d0d0923087fe1f2d85daa68463d221e90b4b8ed0356480c887eea90b2a2cc7ee"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:70cebcf9bc345ac1e034fa781eac3619323eaf87f7bbe26f0e28850beb6f5634"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:890355bf6ba3dc04b5a23d1328eb1f6062165e6262197cebc9acfebdcb23144c"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f0b54b95e39036c116ffc057b3f56f6084ce88822de3d5d1f57fa38554ccf5c1"}, - {file = "bitarray-2.8.2-cp39-cp39-win32.whl", hash = "sha256:b499d93fa31a73e31ee62f2cbe07e4df833fd7151734b8f07c48ffe3e4547ec5"}, - {file = "bitarray-2.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:b007aaf5810c708c5a2778e371aa546d7084e4e9f82f65865b2ce5a182376f42"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b734b074a09b1b2e1de7df423565412d9213faefa8ca422f32be756b189f729"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd074b06be9484040acb4c2c0462c4d19a43e377716be7ba10440f51a57bb98c"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678696bb613f0344b79be385747aae705b327a9a32ace45a353dd16497bc719"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb337ffa10824fa2025c4b1c06a2d809dbed4a4bf9e3ffb262676d084c4e0c50"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2b3c7aa2c9a6533dc7234d2a303efdcb9df3f4ac4d0919ec1caf568868f12a0a"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6765c47b487341837b3731cca3c8033b971ee082f6ab41cb430aa3447686eec"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8566b535bc4ebb26247d6f636a27bb0038bc93fa7e55121628f5cd6b0906ac"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56764825f64ab983d32b8c1d4ee483f415f2559e59388ba266a9fcafc44305bf"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f45f7d58c399e90ee3bddff4f3e2f53ff95c948b2d43de304266153ebd1d778"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:095851409e0db75b1416c8c3e24957135d5a2a206790578e43739e92a00c17c4"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8bb60d5a948f00901da1d7e4953189259b3c7ef79391fecd6f18db3f48a036fe"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2dc483ada55ef35990b67dc0e7a779f0b2ce79d156e452dc8b835b03c0dca9"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a35e308c23f039064600108fc1c8416bd102bc3cf3a6915761a9f7c801237e0"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa49f6cfcae4305d8cff028dc9c9a881189a38f7ca43c085aef894c58cb6fbde"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:111bf9913ebee4630e2cb43b61d0abb39813b231262b114e5268cd6a405a22b9"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b71d82e3f001bcb53463023f7f37e223fff56cf048f577c6d85597db94770f10"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:440c537fdf2eaee7fdd41fb1dce5701c490c1964fdb74225b10b49a7c45bc7b4"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c384c49ce52b82d5b0355000b8aeb7e3a7654997916c1e6fd9d29697edda1076"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27428d7b0e706307d0c697f81599e7af4f52e5873ea6bc269eae3604b16b81fe"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4963982d5da0825768f9a80760a8560c3e4cf711a9a7ea06ff9bcb7bd250b131"}, - {file = "bitarray-2.8.2.tar.gz", hash = "sha256:f90b2f44b5b23364d5fbade2c34652e15b1fcfe813c46f828e008f68a709160f"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, ] [[package]] name = "black" -version = "23.9.1" +version = "23.12.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, - {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, - {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, - {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, - {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, - {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, - {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, - {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, - {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, - {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, - {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -401,40 +439,38 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.28.62" +version = "1.34.30" description = "The AWS SDK for Python" -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "boto3-1.28.62-py3-none-any.whl", hash = "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2"}, - {file = "boto3-1.28.62.tar.gz", hash = "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0"}, + {file = "boto3-1.34.30-py3-none-any.whl", hash = "sha256:cd6173380768faaecf6236dbdcec15d8d032cbb162ce354fdb111056a74fc298"}, + {file = "boto3-1.34.30.tar.gz", hash = "sha256:9e1476ce2b26437881a0381bf2daa54de619ac74ab4bd74278668acda6004a64"}, ] [package.dependencies] -botocore = ">=1.31.62,<1.32.0" +botocore = ">=1.34.30,<1.35.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.7.0,<0.8.0" +s3transfer = ">=0.10.0,<0.11.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.31.62" +version = "1.34.30" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "botocore-1.31.62-py3-none-any.whl", hash = "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7"}, - {file = "botocore-1.31.62.tar.gz", hash = "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c"}, + {file = "botocore-1.34.30-py3-none-any.whl", hash = "sha256:caf82d91c2ff61235284a07ffdfba006873e0752e00896052f901a37720cefa4"}, + {file = "botocore-1.34.30.tar.gz", hash = "sha256:e071a9766e7fc2221ca42ec01dfc54368a7518610787342ea622f6edc57f7891"}, ] [package.dependencies] @@ -443,25 +479,23 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.16.26)"] +crt = ["awscrt (==0.19.19)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -526,7 +560,6 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -536,109 +569,107 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -653,7 +684,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -663,130 +693,125 @@ files = [ [[package]] name = "contourpy" -version = "1.1.1" +version = "1.2.0" description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, - {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, - {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, - {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, - {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, - {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, - {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, - {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, - {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, - {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, - {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, - {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, - {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, - {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, - {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, - {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, - {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, - {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, ] [package.dependencies] -numpy = [ - {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""}, - {version = ">=1.26.0rc1,<2.0", markers = "python_version >= \"3.12\""}, -] +numpy = ">=1.20,<2.0" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "cryptography" -version = "41.0.4" +version = "42.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, - {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, - {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, - {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, + {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, + {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, + {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, + {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, + {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, + {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "cvat-sdk" version = "2.6.0" description = "CVAT REST API" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -813,7 +838,6 @@ pytorch = ["torch", "torchvision"] name = "cycler" version = "0.12.1" description = "Composable style cycles" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -827,105 +851,115 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cytoolz" -version = "0.12.2" +version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cytoolz-0.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bff49986c9bae127928a2f9fd6313146a342bfae8292f63e562f872bd01b871"}, - {file = "cytoolz-0.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:908c13f305d34322e11b796de358edaeea47dd2d115c33ca22909c5e8fb036fd"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:735147aa41b8eeb104da186864b55e2a6623c758000081d19c93d759cd9523e3"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d352d4de060604e605abdc5c8a5d0429d5f156cb9866609065d3003454d4cea"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89247ac220031a4f9f689688bcee42b38fd770d4cce294e5d914afc53b630abe"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9070ae35c410d644e6df98a8f69f3ed2807e657d0df2a26b2643127cbf6944a5"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:843500cd3e4884b92fd4037912bc42d5f047108d2c986d36352e880196d465b0"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a93644d7996fd696ab7f1f466cd75d718d0a00d5c8118b9fe8c64231dc1f85e"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96796594c770bc6587376e74ddc7d9c982d68f47116bb69d90873db5e0ea88b6"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:48425107fbb1af3f0f2410c004f16be10ffc9374358e5600b57fa543f46f8def"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:cde6dbb788a4cbc4a80a72aa96386ba4c2b17bdfff3ace0709799adbe16d6476"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68ae7091cc73a752f0b938f15bb193de80ca5edf5ae2ea6360d93d3e9228357b"}, - {file = "cytoolz-0.12.2-cp310-cp310-win32.whl", hash = "sha256:997b7e0960072f6bb445402da162f964ea67387b9f18bda2361edcc026e13597"}, - {file = "cytoolz-0.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:663911786dcde3e4a5d88215c722c531c7548903dc07d418418c0d1c768072c0"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a7d8b869ded171f6cdf584fc2fc6ae03b30a0e1e37a9daf213a59857a62ed90"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b28787eaf2174e68f0acb3c66f9c6b98bdfeb0930c0d0b08e1941c7aedc8d27"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00547da587f124b32b072ce52dd5e4b37cf199fedcea902e33c67548523e4678"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:275d53fd769df2102d6c9fc98e553bd8a9a38926f54d6b20cf29f0dd00bf3b75"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5556acde785a61d4cf8b8534ae109b023cbd2f9df65ee2afbe070be47c410f8c"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b41a85b9b9a2530b72b0d3d10e383fc3c2647ae88169d557d5e216f881860318"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673d6e9e3aa86949343b46ac2b7be266c36e07ce77fa1d40f349e6987a814d6e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81e6a9a8fda78a2f4901d2915b25bf620f372997ca1f20a14f7cefef5ad6f6f4"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa44215bc31675a6380cd896dadb7f2054a7b94cfb87e53e52af844c65406a54"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a08b4346350660799d81d4016e748bcb134a9083301d41f9618f64a6077f89f2"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2fb740482794a72e2e5fec58e4d9b00dcd5a60a8cef68431ff12f2ba0e0d9a7e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9007bb1290c79402be6b84bcf9e7a622a073859d61fcee146dc7bc47afe328f3"}, - {file = "cytoolz-0.12.2-cp311-cp311-win32.whl", hash = "sha256:a973f5286758f76824ecf19ae1999f6697371a9121c8f163295d181d19a819d7"}, - {file = "cytoolz-0.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:1ce324d1b413636ea5ee929f79637821f13c9e55e9588f38228947294944d2ed"}, - {file = "cytoolz-0.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c08094b9e5d1b6dfb0845a0253cc2655ca64ce70d15162dfdb102e28c8993493"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baf020f4b708f800b353259cd7575e335a79f1ac912d9dda55b2aa0bf3616e42"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4416ee86a87180b6a28e7483102c92debc077bec59c67eda8cc63fc52a218ac0"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ee222671eed5c5b16a5ad2aea07f0a715b8b199ee534834bc1dd2798f1ade7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad92e37be0b106fdbc575a3a669b43b364a5ef334495c9764de4c2d7541f7a99"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460c05238fbfe6d848141669d17a751a46c923f9f0c9fd8a3a462ab737623a44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9e5075e30be626ef0f9bedf7a15f55ed4d7209e832bc314fdc232dbd61dcbf44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:03b58f843f09e73414e82e57f7e8d88f087eaabf8f276b866a40661161da6c51"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e4e612b7ecc9596e7c859cd9e0cd085e6d0c576b4f0d917299595eb56bf9c05"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:08a0e03f287e45eb694998bb55ac1643372199c659affa8319dfbbdec7f7fb3c"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b029bdd5a8b6c9a7c0e8fdbe4fc25ffaa2e09b77f6f3462314696e3a20511829"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win32.whl", hash = "sha256:18580d060fa637ff01541640ecde6de832a248df02b8fb57e6dd578f189d62c7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:97cf514a9f3426228d8daf880f56488330e4b2948a6d183a106921217850d9eb"}, - {file = "cytoolz-0.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18a0f838677f9510aef0330c0096778dd6406d21d4ff9504bf79d85235a18460"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb081b2b02bf4405c804de1ece6f904916838ab0e057f1446e4ac12fac827960"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57233e1600560ceb719bed759dc78393edd541b9a3e7fefc3079abd83c26a6ea"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0295289c4510efa41174850e75bc9188f82b72b1b54d0ea57d1781729c2924d5"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a92aab8dd1d427ac9bc7480cfd3481dbab0ef024558f2f5a47de672d8a5ffaa"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d3495235af09f21aa92a7cdd51504bda640b108b6be834448b774f52852c09"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9c690b359f503f18bf1c46a6456370e4f6f3fc4320b8774ae69c4f85ecc6c94"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:481e3129a76ea01adcc0e7097ccb8dbddab1cfc40b6f0e32c670153512957c0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55e94124af9c8fbb1df54195cc092688fdad0765641b738970b6f1d5ea72e776"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5616d386dfbfba7c39e9418ba668c734f6ceaacc0130877e8a100cad11e6838b"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:732d08228fa8d366fec284f7032cc868d28a99fa81fc71e3adf7ecedbcf33a0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win32.whl", hash = "sha256:f039c5373f7b314b151432c73219216857b19ab9cb834f0eb5d880f74fc7851c"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:246368e983eaee9851b15d7755f82030eab4aa82098d2a34f6bef9c689d33fcc"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81074edf3c74bc9bd250d223408a5df0ff745d1f7a462597536cd26b9390e2d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:960d85ebaa974ecea4e71fa56d098378fa51fd670ee744614cbb95bf95e28fc7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c8d0dff4865da54ae825d43e1721925721b19f3b9aca8e730c2ce73dee2c630"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9d12436fd64937bd2c9609605f527af7f1a8db6e6637639b44121c0fe715d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd461e402e24929d866f05061d2f8337e3a8456e75e21b72c125abff2477c7f7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0568d4da0a9ee9f9f5ab318f6501557f1cfe26d18c96c8e0dac7332ae04c6717"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:101b5bd32badfc8b1f9c7be04ba3ae04fb47f9c8736590666ce9449bff76e0b1"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bb624dbaef4661f5e3625c1e39ad98ecceef281d1380e2774d8084ad0810275"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3e993804e6b04113d61fdb9541b6df2f096ec265a506dad7437517470919c90f"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ab911033e5937fc221a2c165acce7f66ae5ac9d3e54bec56f3c9c197a96be574"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6de6a4bdfaee382c2de2a3580b3ae76fce6105da202bbd835e5efbeae6a9c6e"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9480b4b327be83c4d29cb88bcace761b11f5e30198ffe2287889455c6819e934"}, - {file = "cytoolz-0.12.2-cp38-cp38-win32.whl", hash = "sha256:4180b2785d1278e6abb36a72ac97c92432db53fa2df00ee943d2c15a33627d31"}, - {file = "cytoolz-0.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:d0086ba8d41d73647b13087a3ca9c020f6bfec338335037e8f5172b4c7c8dce5"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d29988bde28a90a00367edcf92afa1a2f7ecf43ea3ae383291b7da6d380ccc25"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24c0d71e9ac91f4466b1bd280f7de43aa4d94682daaf34d85d867a9b479b87cc"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa436abd4ac9ca71859baf5794614e6ec8fa27362f0162baedcc059048da55f7"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c7b4eac7571707269ebc2893facdf87e359cd5c7cfbfa9e6bd8b33fb1079c5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:294d24edc747ef4e1b28e54365f713becb844e7898113fafbe3e9165dc44aeea"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478051e5ef8278b2429864c8d148efcebdc2be948a61c9a44757cd8c816c98f5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14108cafb140dd68fdda610c2bbc6a37bf052cd48cfebf487ed44145f7a2b67f"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fef7b602ccf8a3c77ab483479ccd7a952a8c5bb1c263156671ba7aaa24d1035"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9bf51354e15520715f068853e6ab8190e77139940e8b8b633bdb587956a08fb0"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:388f840fd911d61a96e9e595eaf003f9dc39e847c9060b8e623ab29e556f009b"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a67f75cc51a2dc7229a8ac84291e4d61dc5abfc8940befcf37a2836d95873340"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63b31345e20afda2ae30dba246955517a4264464d75e071fc2fa641e88c763ec"}, - {file = "cytoolz-0.12.2-cp39-cp39-win32.whl", hash = "sha256:f6e86ac2b45a95f75c6f744147483e0fc9697ce7dfe1726083324c236f873f8b"}, - {file = "cytoolz-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:5998f81bf6a2b28a802521efe14d9fc119f74b64e87b62ad1b0e7c3d8366d0c7"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:593e89e2518eaf81e96edcc9ef2c5fca666e8fc922b03d5cb7a7b8964dbee336"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff451d614ca1d4227db0ffa627fb51df71968cf0d9baf0210528dad10fdbc3ab"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad9ea4a50d2948738351790047d45f2b1a023facc01bf0361988109b177e8b2f"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbe038bb78d599b5a29d09c438905defaa615a522bc7e12f8016823179439497"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d494befe648c13c98c0f3d56d05489c839c9228a32f58e9777305deb6c2c1cee"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c26805b6c8dc8565ed91045c44040bf6c0fe5cb5b390c78cd1d9400d08a6cd39"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4e32badb2ccf1773e1e74020b7e3b8caf9e92f842c6be7d14888ecdefc2c6c"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce7889dc3701826d519ede93cdff11940fb5567dbdc165dce0e78047eece02b7"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c820608e7077416f766b148d75e158e454881961881b657cff808529d261dd24"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:698da4fa1f7baeea0607738cb1f9877ed1ba50342b29891b0223221679d6f729"}, - {file = "cytoolz-0.12.2.tar.gz", hash = "sha256:31d4b0455d72d914645f803d917daf4f314d115c70de0578d3820deb8b101f66"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, ] [package.dependencies] @@ -938,7 +972,6 @@ cython = ["cython"] name = "datumaro" version = "0.3" description = "Dataset Management Framework (Datumaro)" -category = "main" optional = false python-versions = ">=3.7" files = [] @@ -981,7 +1014,6 @@ resolved_reference = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11" name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -993,7 +1025,6 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1003,26 +1034,24 @@ files = [ [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] name = "eth-abi" -version = "4.2.1" +version = "5.0.0" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" -category = "main" optional = false -python-versions = ">=3.7.2, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth_abi-4.2.1-py3-none-any.whl", hash = "sha256:abd83410a5326145bf178675c276de0ed154f6dc695dcad1beafaa44d97f44ae"}, - {file = "eth_abi-4.2.1.tar.gz", hash = "sha256:60d88788d53725794cdb07c0f0bb0df2a31a6e1ad19644313fe6117ac24eeeb0"}, + {file = "eth_abi-5.0.0-py3-none-any.whl", hash = "sha256:936a715d7366ac13cac665cbdaf01cc4aabbe8c2d810d716287a9634f9665e01"}, + {file = "eth_abi-5.0.0.tar.gz", hash = "sha256:89c4454d794d9ed92ad5cb2794698c5cee6b7b3ca6009187d0e282adc7f9b6dc"}, ] [package.dependencies] @@ -1031,22 +1060,20 @@ eth-utils = ">=2.0.0" parsimonious = ">=0.9.0,<0.10.0" [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)"] tools = ["hypothesis (>=4.18.2,<5.0.0)"] [[package]] name = "eth-account" -version = "0.9.0" +version = "0.10.0" description = "eth-account: Sign Ethereum transactions and messages with local private keys" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ - {file = "eth-account-0.9.0.tar.gz", hash = "sha256:5f66ecb7bc52569924dfaf4a9add501b1c2a4901eec74e3c0598cd26d0971777"}, - {file = "eth_account-0.9.0-py3-none-any.whl", hash = "sha256:35636ca14e9063dea233648703338be1a44e8cb1a2f9de1519d2b1be4655da59"}, + {file = "eth-account-0.10.0.tar.gz", hash = "sha256:474a2fccf7286230cf66502565f03b536921d7e1fdfceba198e42160e5ac4bc1"}, + {file = "eth_account-0.10.0-py3-none-any.whl", hash = "sha256:b7a83f506a8edf57926569e5f04471ce3f1700e572d3421b4ad0dad7a26c0978"}, ] [package.dependencies] @@ -1056,7 +1083,7 @@ eth-keyfile = ">=0.6.0" eth-keys = ">=0.4.0" eth-rlp = ">=0.3.0" eth-utils = ">=2.0.0" -hexbytes = ">=0.1.0" +hexbytes = ">=0.1.0,<0.4.0" rlp = ">=1.0.0" [package.extras] @@ -1067,127 +1094,114 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdis [[package]] name = "eth-hash" -version = "0.5.2" +version = "0.6.0" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-hash-0.5.2.tar.gz", hash = "sha256:1b5f10eca7765cc385e1430eefc5ced6e2e463bb18d1365510e2e539c1a6fe4e"}, - {file = "eth_hash-0.5.2-py3-none-any.whl", hash = "sha256:251f62f6579a1e247561679d78df37548bd5f59908da0b159982bf8293ad32f0"}, + {file = "eth-hash-0.6.0.tar.gz", hash = "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478"}, + {file = "eth_hash-0.6.0-py3-none-any.whl", hash = "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3"}, ] [package.dependencies] pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] pycryptodome = ["pycryptodome (>=3.6.6,<4)"] pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-keyfile" -version = "0.6.1" -description = "A library for handling the encrypted keyfiles used to store ethereum private keys." -category = "main" +version = "0.7.0" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" optional = false -python-versions = "*" +python-versions = ">=3.8, <4" files = [ - {file = "eth-keyfile-0.6.1.tar.gz", hash = "sha256:471be6e5386fce7b22556b3d4bde5558dbce46d2674f00848027cb0a20abdc8c"}, - {file = "eth_keyfile-0.6.1-py3-none-any.whl", hash = "sha256:609773a1ad5956944a33348413cad366ec6986c53357a806528c8f61c4961560"}, + {file = "eth-keyfile-0.7.0.tar.gz", hash = "sha256:6bdb8110c3a50439deb68a04c93c9d5ddd5402353bfae1bf4cfca1d6dff14fcf"}, + {file = "eth_keyfile-0.7.0-py3-none-any.whl", hash = "sha256:6a89b231a2fe250c3a8f924f2695bb9cce33ddd0d6f7ebbcdacd183d7f83d537"}, ] [package.dependencies] -eth-keys = ">=0.4.0,<0.5.0" -eth-utils = ">=2,<3" +eth-keys = ">=0.4.0" +eth-utils = ">=2" pycryptodome = ">=3.6.6,<4" [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "flake8 (==4.0.1)", "idna (==2.7)", "pluggy (>=1.0.0,<2)", "pycryptodome (>=3.6.6,<4)", "pytest (>=6.2.5,<7)", "requests (>=2.20,<3)", "setuptools (>=38.6.0)", "tox (>=2.7.0)", "twine", "wheel"] -keyfile = ["eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "pycryptodome (>=3.6.6,<4)"] -lint = ["flake8 (==4.0.1)"] -test = ["pytest (>=6.2.5,<7)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-keys" -version = "0.4.0" -description = "Common API for Ethereum key operations." -category = "main" +version = "0.5.0" +description = "eth-keys: Common API for Ethereum key operations" optional = false -python-versions = "*" +python-versions = ">=3.8, <4" files = [ - {file = "eth-keys-0.4.0.tar.gz", hash = "sha256:7d18887483bc9b8a3fdd8e32ddcb30044b9f08fcb24a380d93b6eee3a5bb3216"}, - {file = "eth_keys-0.4.0-py3-none-any.whl", hash = "sha256:e07915ffb91277803a28a379418bdd1fad1f390c38ad9353a0f189789a440d5d"}, + {file = "eth-keys-0.5.0.tar.gz", hash = "sha256:a0abccb83f3d84322591a2c047a1e3aa52ea86b185fa3e82ce311d120ca2791e"}, + {file = "eth_keys-0.5.0-py3-none-any.whl", hash = "sha256:b2bed3ff3bcede68cc0cd4458c7147baaeaac1211a1efdb6ca019f9d3d989f2b"}, ] [package.dependencies] -eth-typing = ">=3.0.0,<4" -eth-utils = ">=2.0.0,<3.0.0" +eth-typing = ">=3" +eth-utils = ">=2" [package.extras] -coincurve = ["coincurve (>=7.0.0,<16.0.0)"] -dev = ["asn1tools (>=0.146.2,<0.147)", "bumpversion (==0.5.3)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (>=3.0.1,<3.1)", "flake8 (==3.0.4)", "hypothesis (>=5.10.3,<6.0.0)", "mypy (==0.782)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)", "tox (==3.20.0)", "twine"] -eth-keys = ["eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)"] -lint = ["flake8 (==3.0.4)", "mypy (==0.782)"] -test = ["asn1tools (>=0.146.2,<0.147)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "factory-boy (>=3.0.1,<3.1)", "hypothesis (>=5.10.3,<6.0.0)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)"] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3,<6)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3,<6)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] [[package]] name = "eth-rlp" -version = "0.3.0" +version = "1.0.1" description = "eth-rlp: RLP definitions for common Ethereum objects in Python" -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-rlp-0.3.0.tar.gz", hash = "sha256:f3263b548df718855d9a8dbd754473f383c0efc82914b0b849572ce3e06e71a6"}, - {file = "eth_rlp-0.3.0-py3-none-any.whl", hash = "sha256:e88e949a533def85c69fa94224618bbbd6de00061f4cff645c44621dab11cf33"}, + {file = "eth-rlp-1.0.1.tar.gz", hash = "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027"}, + {file = "eth_rlp-1.0.1-py3-none-any.whl", hash = "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517"}, ] [package.dependencies] -eth-utils = ">=2.0.0,<3" +eth-utils = ">=2.0.0" hexbytes = ">=0.1.0,<1" -rlp = ">=0.6.0,<4" +rlp = ">=0.6.0" +typing-extensions = {version = ">=4.0.1", markers = "python_version <= \"3.11\""} [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash[pycryptodome]", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] -lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)"] -test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.14.6)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-typing" -version = "3.5.0" +version = "4.0.0" description = "eth-typing: Common type annotations for ethereum python packages" -category = "main" optional = false -python-versions = ">=3.7.2, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-typing-3.5.0.tar.gz", hash = "sha256:a92f6896896752143a4704c57441eedf7b1f65d5df4b1c20cb802bb4aa602d7e"}, - {file = "eth_typing-3.5.0-py3-none-any.whl", hash = "sha256:a773dbb7d78fcd1539c30264193ca26ec965f3abca2711748e307f117b0a10f5"}, + {file = "eth-typing-4.0.0.tar.gz", hash = "sha256:9af0b6beafbc5c2e18daf19da5f5a68315023172c4e79d149e12ad10a3d3f731"}, + {file = "eth_typing-4.0.0-py3-none-any.whl", hash = "sha256:7e556bea322b6e8c0a231547b736c258e10ce9eed5ddc254f51031b12af66a16"}, ] -[package.dependencies] -typing-extensions = ">=4.0.1" - [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-utils" -version = "2.2.1" +version = "3.0.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -category = "main" optional = false -python-versions = ">=3.7,<4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-utils-2.2.1.tar.gz", hash = "sha256:f79a95f86dd991344697c763db40271dbe43fbbcd5776f49b0c4fb7b645ee1c4"}, - {file = "eth_utils-2.2.1-py3-none-any.whl", hash = "sha256:60fc999c1b4ae011ab600b01a3eb5375156f3bc46e7cd1a83ca9e6e14bb9b13c"}, + {file = "eth-utils-3.0.0.tar.gz", hash = "sha256:8721869568448349bceae63c277b75758d11e0dc190e7ef31e161b89619458f1"}, + {file = "eth_utils-3.0.0-py3-none-any.whl", hash = "sha256:9a284106acf6f6ce91ddf792489cf8bd4c681fd5ae7653d2f3d5d100be5c3905"}, ] [package.dependencies] @@ -1197,21 +1211,19 @@ eth-typing = ">=3.0.0" toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==3.8.3)", "hypothesis (>=4.43.0)", "ipython", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] -test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.5.1)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -1221,7 +1233,6 @@ test = ["pytest (>=6)"] name = "fastapi" version = "0.97.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1238,77 +1249,75 @@ all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "filelock" -version = "3.12.4" +version = "3.13.1" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, - {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] -typing = ["typing-extensions (>=4.7.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "fonttools" -version = "4.43.1" +version = "4.47.2" description = "Tools to manipulate font files" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, - {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, - {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, - {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, - {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, - {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, - {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, - {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, - {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, - {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, - {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, - {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, - {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"}, + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c"}, + {file = "fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0"}, + {file = "fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703"}, + {file = "fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c"}, + {file = "fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f"}, + {file = "fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085"}, + {file = "fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:254d9a6f7be00212bf0c3159e0a420eb19c63793b2c05e049eb337f3023c5ecc"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eabae77a07c41ae0b35184894202305c3ad211a93b2eb53837c2a1143c8bc952"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86a5ab2873ed2575d0fcdf1828143cfc6b977ac448e3dc616bb1e3d20efbafa"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13819db8445a0cec8c3ff5f243af6418ab19175072a9a92f6cc8ca7d1452754b"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4e743935139aa485fe3253fc33fe467eab6ea42583fa681223ea3f1a93dd01e6"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d49ce3ea7b7173faebc5664872243b40cf88814ca3eb135c4a3cdff66af71946"}, + {file = "fonttools-4.47.2-cp38-cp38-win32.whl", hash = "sha256:94208ea750e3f96e267f394d5588579bb64cc628e321dbb1d4243ffbc291b18b"}, + {file = "fonttools-4.47.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f750037e02beb8b3569fbff701a572e62a685d2a0e840d75816592280e5feae"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d71606c9321f6701642bd4746f99b6089e53d7e9817fc6b964e90d9c5f0ecc6"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e0427864c6c91cf77f16d1fb9bf1bbf7453e824589e8fb8461b6ee1144f506"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a00bd0e68e88987dcc047ea31c26d40a3c61185153b03457956a87e39d43c37"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d77479fb885ef38a16a253a2f4096bc3d14e63a56d6246bfdb56365a12b20c"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5465df494f20a7d01712b072ae3ee9ad2887004701b95cb2cc6dcb9c2c97a899"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c811d3c73b6abac275babb8aa439206288f56fdb2c6f8835e3d7b70de8937a7"}, + {file = "fonttools-4.47.2-cp39-cp39-win32.whl", hash = "sha256:5b60e3afa9635e3dfd3ace2757039593e3bd3cf128be0ddb7a1ff4ac45fa5a50"}, + {file = "fonttools-4.47.2-cp39-cp39-win_amd64.whl", hash = "sha256:7ee48bd9d6b7e8f66866c9090807e3a4a56cf43ffad48962725a190e0dd774c8"}, + {file = "fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184"}, + {file = "fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres", "pycairo", "scipy"] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] @@ -1316,85 +1325,99 @@ repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] +unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "frozenlist" -version = "1.4.0" +version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, - {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, - {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, - {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, - {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1403,85 +1426,79 @@ files = [ [[package]] name = "greenlet" -version = "3.0.0" +version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, - {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, - {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, - {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, - {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, - {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, - {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, - {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, - {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, - {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, - {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, - {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, - {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, - {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, - {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, - {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, - {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, - {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, -] - -[package.extras] -docs = ["Sphinx"] + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1493,7 +1510,6 @@ files = [ name = "h5py" version = "3.10.0" description = "Read and write HDF5 files from Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1531,7 +1547,6 @@ numpy = ">=1.17.3" name = "hexbytes" version = "0.3.1" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -1549,7 +1564,6 @@ test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>= name = "httpcore" version = "0.17.3" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1561,17 +1575,16 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httpx" version = "0.24.1" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1587,20 +1600,19 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "human-protocol-sdk" -version = "1.1.15" +version = "1.1.19" description = "A python library to launch escrow contracts to the HUMAN network." -category = "main" optional = false python-versions = "*" files = [ - {file = "human-protocol-sdk-1.1.15.tar.gz", hash = "sha256:b5bdd8072b5354e548a6631967a41a81f2c8d79acad08609ef798e21637e9c53"}, - {file = "human_protocol_sdk-1.1.15-py3-none-any.whl", hash = "sha256:93c82d98d9bc3cd574306dcfadcfabf117b6d8cdb7d4abafb29a7aa5c7ae5a15"}, + {file = "human-protocol-sdk-1.1.19.tar.gz", hash = "sha256:c00fb0bc58607c57a062d78f916af793d436da9c84a05030261e08c930b5268d"}, + {file = "human_protocol_sdk-1.1.19-py3-none-any.whl", hash = "sha256:725d3ef50d7d23b6a90cadf14e2cfd59bc698beed9bcda239b55141b1ad6e3e1"}, ] [package.dependencies] @@ -1609,21 +1621,20 @@ cryptography = "*" minio = "*" pgpy = "*" validators = "0.20.0" -web3 = ">=6.8.0,<6.9.0" +web3 = "==6.8.*" [package.extras] agreement = ["numpy", "pyerf"] [[package]] name = "identify" -version = "2.5.30" +version = "2.5.33" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, - {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, ] [package.extras] @@ -1631,21 +1642,19 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1655,27 +1664,22 @@ files = [ [[package]] name = "isort" -version = "5.12.0" +version = "5.13.2" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1685,14 +1689,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.19.1" +version = "4.21.1" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, - {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, ] [package.dependencies] @@ -1707,24 +1710,22 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.7.1" +version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, - {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, ] [package.dependencies] -referencing = ">=0.28.0" +referencing = ">=0.31.0" [[package]] name = "kiwisolver" version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1836,94 +1837,92 @@ files = [ [[package]] name = "lru-dict" -version = "1.2.0" +version = "1.3.0" description = "An Dict like LRU container." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, - {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, - {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, - {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, - {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, - {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, - {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, - {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, - {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, - {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, - {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, - {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, - {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, - {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, - {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, - {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, - {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, - {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, - {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, + {file = "lru-dict-1.3.0.tar.gz", hash = "sha256:54fd1966d6bd1fcde781596cb86068214edeebff1db13a2cea11079e3fd07b6b"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4073333894db9840f066226d50e6f914a2240711c87d60885d8c940b69a6673f"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0ad6361e4dd63b47b2fc8eab344198f37387e1da3dcfacfee19bafac3ec9f1eb"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c637ab54b8cd9802fe19b260261e38820d748adf7606e34045d3c799b6dde813"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fce5f95489ca1fc158cc9fe0f4866db9cec82c2be0470926a9080570392beaf"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2bf2e24cf5f19c3ff69bf639306e83dced273e6fa775b04e190d7f5cd16f794"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90059f7701bef3c4da073d6e0434a9c7dc551d5adce30e6b99ef86b186f4b4a"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ecb7ae557239c64077e9b26a142eb88e63cddb104111a5122de7bebbbd00098"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6af36166d22dba851e06a13e35bbf33845d3dd88872e6aebbc8e3e7db70f4682"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee38d420c77eed548df47b7d74b5169a98e71c9e975596e31ab808e76d11f09"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0e1845024c31e6ff246c9eb5e6f6f1a8bb564c06f8a7d6d031220044c081090b"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ca5474b1649555d014be1104e5558a92497509021a5ba5ea6e9b492303eb66b"}, + {file = "lru_dict-1.3.0-cp310-cp310-win32.whl", hash = "sha256:ebb03a9bd50c2ed86d4f72a54e0aae156d35a14075485b2127c4b01a3f4a63fa"}, + {file = "lru_dict-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:04cda617f4e4c27009005d0a8185ef02829b14b776d2791f5c994cc9d668bc24"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:20c595764695d20bdc3ab9b582e0cc99814da183544afb83783a36d6741a0dac"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9b30a8f50c3fa72a494eca6be5810a1b5c89e4f0fda89374f0d1c5ad8d37d51"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9710737584650a4251b9a566cbb1a86f83437adb209c9ba43a4e756d12faf0d7"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b84c321ae34f2f40aae80e18b6fa08b31c90095792ab64bb99d2e385143effaa"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eed24272b4121b7c22f234daed99899817d81d671b3ed030c876ac88bc9dc890"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd13af06dab7c6ee92284fd02ed9a5613a07d5c1b41948dc8886e7207f86dfd"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1efc59bfba6aac33684d87b9e02813b0e2445b2f1c444dae2a0b396ad0ed60c"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfaf75ac574447afcf8ad998789071af11d2bcf6f947643231f692948839bd98"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c95f8751e2abd6f778da0399c8e0239321d560dbc58cb063827123137d213242"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:abd0c284b26b5c4ee806ca4f33ab5e16b4bf4d5ec9e093e75a6f6287acdde78e"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a47740652b25900ac5ce52667b2eade28d8b5fdca0ccd3323459df710e8210a"}, + {file = "lru_dict-1.3.0-cp311-cp311-win32.whl", hash = "sha256:a690c23fc353681ed8042d9fe8f48f0fb79a57b9a45daea2f0be1eef8a1a4aa4"}, + {file = "lru_dict-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:efd3f4e0385d18f20f7ea6b08af2574c1bfaa5cb590102ef1bee781bdfba84bc"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c279068f68af3b46a5d649855e1fb87f5705fe1f744a529d82b2885c0e1fc69d"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:350e2233cfee9f326a0d7a08e309372d87186565e43a691b120006285a0ac549"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4eafb188a84483b3231259bf19030859f070321b00326dcb8e8c6cbf7db4b12f"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73593791047e36b37fdc0b67b76aeed439fcea80959c7d46201240f9ec3b2563"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1958cb70b9542773d6241974646e5410e41ef32e5c9e437d44040d59bd80daf2"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc1cd3ed2cee78a47f11f3b70be053903bda197a873fd146e25c60c8e5a32cd6"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82eb230d48eaebd6977a92ddaa6d788f14cf4f4bcf5bbffa4ddfd60d051aa9d4"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5ad659cbc349d0c9ba8e536b5f40f96a70c360f43323c29f4257f340d891531c"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba490b8972531d153ac0d4e421f60d793d71a2f4adbe2f7740b3c55dce0a12f1"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:c0131351b8a7226c69f1eba5814cbc9d1d8daaf0fdec1ae3f30508e3de5262d4"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e88dba16695f17f41701269fa046197a3fd7b34a8dba744c8749303ddaa18df"}, + {file = "lru_dict-1.3.0-cp312-cp312-win32.whl", hash = "sha256:6ffaf595e625b388babc8e7d79b40f26c7485f61f16efe76764e32dce9ea17fc"}, + {file = "lru_dict-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c265f16c936a8ff3bb4b8a4bda0be94c15ec28b63e99fdb1439c1ffe4cd437db"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:784ca9d3b0730b3ec199c0a58f66264c63dd5d438119c739c349a6a9be8e5f6e"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e13b2f58f647178470adaa14603bb64cc02eeed32601772ccea30e198252883c"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ffbce5c2e80f57937679553c8f27e61ec327c962bf7ea0b15f1d74277fd5363"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7969cb034b3ccc707aff877c73c225c32d7e2a7981baa8f92f5dd4d468fe8c33"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca9ab676609cce85dd65d91c275e47da676d13d77faa72de286fbea30fbaa596"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27c078b5d75989952acbf9b77e14c3dadc468a4aafe85174d548afbc5efc38b"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6123aefe97762ad74215d05320a7f389f196f0594c8813534284d4eafeca1a96"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd869cadba9a63e1e7fe2dced4a5747d735135b86016b0a63e8c9e324ab629ac"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40a8daddc29c7edb09dfe44292cf111f1e93a8344349778721d430d336b50505"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a03170e4152836987a88dcebde61aaeb73ab7099a00bb86509d45b3fe424230"}, + {file = "lru_dict-1.3.0-cp38-cp38-win32.whl", hash = "sha256:3b4f121afe10f5a82b8e317626eb1e1c325b3f104af56c9756064cd833b1950b"}, + {file = "lru_dict-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:1470f5828c7410e16c24b5150eb649647986e78924816e6fb0264049dea14a2b"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c9f746a9917e784fffcedeac4c8c47a3dbd90cbe13b69e9140182ad97ce4b7"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2789296819525a1f3204072dfcf3df6db8bcf69a8fc740ffd3de43a684ea7002"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:170b66d29945391460351588a7bd8210a95407ae82efe0b855e945398a1d24ea"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774ca88501a9effe8797c3db5a6685cf20978c9cb0fe836b6813cfe1ca60d8c9"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df2e119c6ae412d2fd641a55f8a1e2e51f45a3de3449c18b1b86c319ab79e0c4"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28aa1ea42a7e48174bf513dc2416fea7511a547961e678dc6f5670ca987c18cb"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9537e1cee6fa582cb68f2fb9ce82d51faf2ccc0a638b275d033fdcb1478eb80b"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:64545fca797fe2c68c5168efb5f976c6e1459e058cab02445207a079180a3557"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a193a14c66cfc0c259d05dddc5e566a4b09e8f1765e941503d065008feebea9d"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb1de0ce4137b060abaafed8474cc0ebd12cedd88aaa7f7b3ebb1ddfba86ae0"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8551ccab1349d4bebedab333dfc8693c74ff728f4b565fe15a6bf7d296bd7ea9"}, + {file = "lru_dict-1.3.0-cp39-cp39-win32.whl", hash = "sha256:6cb0be5e79c3f34d69b90d8559f0221e374b974b809a22377122c4b1a610ff67"}, + {file = "lru_dict-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9f725f2a0bdf1c18735372d5807af4ea3b77888208590394d4660e3d07971f21"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f8f7824db5a64581180ab9d09842e6dd9fcdc46aac9cb592a0807cd37ea55680"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acd04b7e7b0c0c192d738df9c317093335e7282c64c9d1bb6b7ebb54674b4e24"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5c20f236f27551e3f0adbf1a987673fb1e9c38d6d284502cd38f5a3845ef681"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca3703ff03b03a1848c563bc2663d0ad813c1cd42c4d9cf75b623716d4415d9a"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a9fb71ba262c6058a0017ce83d343370d0a0dbe2ae62c2eef38241ec13219330"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f5b88a7c39e307739a3701194993455968fcffe437d1facab93546b1b8a334c1"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2682bfca24656fb7a643621520d57b7fe684ed5fa7be008704c1235d38e16a32"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96fc87ddf569181827458ec5ad8fa446c4690cffacda66667de780f9fcefd44d"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec98e2c7da7631f0811730303abc4bdfe70d013f7a11e174a2ccd5612a7c59"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6bba2863060caeaedd8386b0c8ee9a7ce4d57a7cb80ceeddf440b4eff2d013ba"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c497fb60279f1e1d7dfbe150b1b069eaa43f7e172dab03f206282f4994676c5"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9509d817a47597988615c1a322580c10100acad10c98dfcf3abb41e0e5877f"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0213ab4e3d9a8d386c18e485ad7b14b615cb6f05df6ef44fb2a0746c6ea9278b"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50fbd69cd3287196796ab4d50e4cc741eb5b5a01f89d8e930df08da3010c385"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5247d1f011f92666010942434020ddc5a60951fefd5d12a594f0e5d9f43e3b3b"}, ] [package.extras] @@ -1931,122 +1930,106 @@ test = ["pytest"] [[package]] name = "lxml" -version = "4.9.3" +version = "5.1.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" files = [ - {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, - {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, - {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, - {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, - {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, - {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, + {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, + {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, + {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, + {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, + {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, + {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, + {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, + {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, + {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, + {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, + {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, + {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, + {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, + {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, + {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, + {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, + {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.35)"] +source = ["Cython (>=3.0.7)"] [[package]] name = "mako" -version = "1.2.4" +version = "1.3.2" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, - {file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, + {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, + {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, ] [package.dependencies] @@ -2059,135 +2042,143 @@ testing = ["pytest"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] name = "matplotlib" -version = "3.8.0" +version = "3.8.2" description = "Python plotting package" -category = "main" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a"}, - {file = "matplotlib-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39"}, - {file = "matplotlib-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c"}, - {file = "matplotlib-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732"}, - {file = "matplotlib-3.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900"}, - {file = "matplotlib-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196"}, - {file = "matplotlib-3.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e"}, - {file = "matplotlib-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06"}, - {file = "matplotlib-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087"}, - {file = "matplotlib-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99"}, - {file = "matplotlib-3.8.0.tar.gz", hash = "sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, + {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, + {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, + {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, + {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, + {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, + {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, + {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, + {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, + {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, ] [package.dependencies] contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" -kiwisolver = ">=1.0.1" +kiwisolver = ">=1.3.1" numpy = ">=1.21,<2" packaging = ">=20.0" -pillow = ">=6.2.0" +pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" -setuptools_scm = ">=7" [[package]] name = "minio" -version = "7.1.17" +version = "7.2.3" description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" -category = "main" optional = false python-versions = "*" files = [ - {file = "minio-7.1.17-py3-none-any.whl", hash = "sha256:0aa525d77a3bc61378444c2400b0ba2685ad4cd6ecb3fba4141a0d0765e25f40"}, - {file = "minio-7.1.17.tar.gz", hash = "sha256:b0b687c1ec9be422a1f8b04c65fb8e43a1c090f9508178db57c434a17341c404"}, + {file = "minio-7.2.3-py3-none-any.whl", hash = "sha256:e6b5ce0a9b4368da50118c3f0c4df5dbf33885d44d77fce6c0aa1c485e6af7a1"}, + {file = "minio-7.2.3.tar.gz", hash = "sha256:4971dfb1a71eeefd38e1ce2dc7edc4e6eb0f07f1c1d6d70c15457e3280cfc4b9"}, ] [package.dependencies] +argon2-cffi = "*" certifi = "*" +pycryptodome = "*" +typing-extensions = "*" urllib3 = "*" [[package]] name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2271,7 +2262,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2281,58 +2271,55 @@ files = [ [[package]] name = "networkx" -version = "3.1" +version = "3.2.1" description = "Python package for creating and manipulating graphs and networks" -category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, - {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, + {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, + {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, ] [package.extras] -default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] -developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] -test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "nibabel" -version = "5.1.0" +version = "5.2.0" description = "Access a multitude of neuroimaging data formats" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "nibabel-5.1.0-py3-none-any.whl", hash = "sha256:b3deb8130c835b9d26e80880b0d5e443d9e3f30972b3b0302dd2fafa3ca629f8"}, - {file = "nibabel-5.1.0.tar.gz", hash = "sha256:ce73ca5e957209e7219a223cb71f77235c9df2acf4d3f27f861ba38e9481ac53"}, + {file = "nibabel-5.2.0-py3-none-any.whl", hash = "sha256:77724af6e29fd9c4173702e4d031e7d8c45b5963887905a0f90edab880381b7f"}, + {file = "nibabel-5.2.0.tar.gz", hash = "sha256:3df8f1ab981d1bd92f4331d565528d126ab9717fdbd4cfe68f43fcd1c2bf3f52"}, ] [package.dependencies] -numpy = ">=1.19" +numpy = ">=1.20" packaging = ">=17" [package.extras] -all = ["nibabel[dev,dicomfs,doc,minc2,spm,style,test,zstd]"] -dev = ["gitpython", "nibabel[style]", "twine"] +all = ["nibabel[dicomfs,minc2,spm,zstd]"] +dev = ["tox"] dicom = ["pydicom (>=1.0.0)"] dicomfs = ["nibabel[dicom]", "pillow"] -doc = ["matplotlib (>=1.5.3)", "numpydoc", "sphinx (>=5.3,<6.0)", "texext", "tomli"] -doctest = ["nibabel[doc,test]"] +doc = ["matplotlib (>=1.5.3)", "numpydoc", "sphinx", "texext", "tomli"] +doctest = ["tox"] minc2 = ["h5py"] spm = ["scipy"] -style = ["blue", "flake8", "isort"] -test = ["coverage", "pytest (!=5.3.4)", "pytest-cov", "pytest-doctestplus", "pytest-httpserver", "pytest-xdist"] -typing = ["importlib-resources", "mypy", "pydicom", "pytest", "pyzstd", "types-pillow", "types-setuptools"] +style = ["tox"] +test = ["pytest", "pytest-cov", "pytest-doctestplus", "pytest-httpserver", "pytest-xdist"] +typing = ["tox"] zstd = ["pyzstd (>=0.14.3)"] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -2345,173 +2332,136 @@ setuptools = "*" [[package]] name = "numpy" -version = "1.25.2" +version = "1.26.3" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, -] - -[[package]] -name = "numpy" -version = "1.26.1" -description = "Fundamental package for array computing in Python" -category = "main" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, - {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, - {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, - {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, - {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, - {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, - {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, - {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, - {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, - {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, - {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, - {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, - {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, - {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, - {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, - {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, - {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, - {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, - {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, - {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, - {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, - {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, - {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, - {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, - {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, - {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, - {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, - {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, - {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, - {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, + {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, + {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, + {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, + {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, + {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, + {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, + {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, + {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, + {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, ] [[package]] name = "opencv-python" -version = "4.8.1.78" +version = "4.9.0.80" description = "Wrapper package for OpenCV python bindings." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-4.8.1.78.tar.gz", hash = "sha256:cc7adbbcd1112877a39274106cb2752e04984bc01a031162952e97450d6117f6"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:91d5f6f5209dc2635d496f6b8ca6573ecdad051a09e6b5de4c399b8e673c60da"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31f47e05447da8b3089faa0a07ffe80e114c91ce0b171e6424f9badbd1c5cd"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9814beca408d3a0eca1bae7e3e5be68b07c17ecceb392b94170881216e09b319"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c406bdb41eb21ea51b4e90dfbc989c002786c3f601c236a99c59a54670a394"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:a7aac3900fbacf55b551e7b53626c3dad4c71ce85643645c43e91fcb19045e47"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956"}, + {file = "opencv-python-4.9.0.80.tar.gz", hash = "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0"}, ] [package.dependencies] numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, + {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, ] [[package]] name = "orjson" -version = "3.9.8" +version = "3.9.12" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.8-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:823525bfb27b804b492acc59a45dc0973ea629d97557eac81dde7b34b5267611"}, - {file = "orjson-3.9.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6f2634fe6c88a0e1e785fc0b6845ad75bef6e20f1ee3d62fd81b17e7505cbf"}, - {file = "orjson-3.9.8-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c56dd62754e2ee5b7f64d37f3e85685d3bd5bcaa448076e9113be9069078dfc"}, - {file = "orjson-3.9.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c863c7805a7961428a40431a8f47c3f71c74e6c5ddf1ab023e6e79bc5806e6d5"}, - {file = "orjson-3.9.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d30621cf18a0e16a16fbcf2fa536d800f78514a46f5321130f1b54e88994267"}, - {file = "orjson-3.9.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5311ce1457a29084146d2599588dc8ad96256feb921af8e365444fa8ad67afac"}, - {file = "orjson-3.9.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9b070c895fc81c362b1b41dc6d0c81a84ee4abb1193804de15683549aeeb0ee"}, - {file = "orjson-3.9.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:24915b65ac19731a57a5ab7dbf463f91555e10d4ad833513e7d8cc6848487c24"}, - {file = "orjson-3.9.8-cp310-none-win32.whl", hash = "sha256:2bcc9dc53f9e1d679515349bf299ed5e75310146c755d2ba227a7e37851ab3fb"}, - {file = "orjson-3.9.8-cp310-none-win_amd64.whl", hash = "sha256:423774c85e73054acfef10fc3328f35c8d3e0193a7247d47308ebfccde70695d"}, - {file = "orjson-3.9.8-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8a1c92f467f5fd0f8fb79273006b563364b1e45667b3760423498348dc2e22fa"}, - {file = "orjson-3.9.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742d4d16d66579ffff4b2048a8de4a0b03d731847233e92c4edd418a9c582d0f"}, - {file = "orjson-3.9.8-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d1aab08b373232f568ea9ae048f9f77e09f389068afee6dd44bb6140e2c3ea3"}, - {file = "orjson-3.9.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ed63273ec4ecdd7865e9d984d65a749c0d780882cf9dde6ab2bc6323f6471a"}, - {file = "orjson-3.9.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d23edcb32383f3d86b2f4914f9825ce2d67625abd34be6e5ed1f59ec30127b7a"}, - {file = "orjson-3.9.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9bcd3a48b260d3dfe68b8ce93d11f99a70bd4c908efe22d195a1b1dcfb15ac2"}, - {file = "orjson-3.9.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9ce982f3c1df83f7dc74f3b2690605470ff4790d12558e44359f01e822c5cb08"}, - {file = "orjson-3.9.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4433dd903d5b022a64e9dd1dca94f08ab04d5d928a0ecd33dd46110468960879"}, - {file = "orjson-3.9.8-cp311-none-win32.whl", hash = "sha256:a119c73520192c2882d0549151b9cdd65e0bb5396bedf8951ba5f70d6a873879"}, - {file = "orjson-3.9.8-cp311-none-win_amd64.whl", hash = "sha256:764306f6370e6c76cbbf3139dd9b05be9c4481ee0b15966bd1907827a5777216"}, - {file = "orjson-3.9.8-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af8e6185516ce0c93d6ce1f4105918504da629c631fd969686f32a1be3ed3c9b"}, - {file = "orjson-3.9.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e8f5ac250184dcb6b00543f0f82853d7e840e476d0135733e459aee058695e5"}, - {file = "orjson-3.9.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:edafb45fc5b2063abd8a0baf6be21c38497df2d9e0b75cdb053eb0ff100fa26c"}, - {file = "orjson-3.9.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc449bff1d4152438615f4a6a003577942908c4e166d64dc46d1f3f0cde72ecd"}, - {file = "orjson-3.9.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee887aeb8ab0c1d25e9f2b540f9a34b4cbfe8894f95b63a5984441a9f337d2ff"}, - {file = "orjson-3.9.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:235b4aa46c58ded90c8b368722c1eb941613fe5a6b18bc14cfaae929f0be902e"}, - {file = "orjson-3.9.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ff2e6e429416b6287006ba0556083f62396199299ab85afd3ba1e83be14677e2"}, - {file = "orjson-3.9.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab9c234bfe89aeba825feb897718c65a80851f367a4a8308d6b5074a80fce6e5"}, - {file = "orjson-3.9.8-cp312-none-win_amd64.whl", hash = "sha256:5c818f19315251d68954c529f5d8322053f1c35b500b47d008e968bf2d32ed97"}, - {file = "orjson-3.9.8-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e6a267c0fc64fc4d0b8fb146e1a060a40f570441a9390ec4bc6de0b5fda148cd"}, - {file = "orjson-3.9.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3c7c4d60e21b0f10c8214d7ca9f2243019dd1bf9d2750b3b4a9250935977a24"}, - {file = "orjson-3.9.8-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3be3da93c4d044d2f60de816320087a8494c3e75cdf3369655e014240b1a229d"}, - {file = "orjson-3.9.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0619df2454b87d883f7f9ea95d79fc21fec0b8a4d600b549a1e91f59a3493d6b"}, - {file = "orjson-3.9.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:119a6edcecef4e37d30d6998e9cedd9e0ecdc894fa07216221dc8dd2eb24dd9d"}, - {file = "orjson-3.9.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32ac29f9c30cc152e7432a26c665232a382678f2402bf782f73fbc985cfb37e"}, - {file = "orjson-3.9.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:002f7ca314cc8fbed5f00990bf48eda098ba1bba1e0c23be4bb024381e7889d1"}, - {file = "orjson-3.9.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e538974e2ed20504f3dad0bcdab41cd5e4fa086dabea852a150e4cc98293183d"}, - {file = "orjson-3.9.8-cp38-none-win32.whl", hash = "sha256:9df23493a72f073b2ab1005e628a963248dc577a2816e9c82caf09ff74908414"}, - {file = "orjson-3.9.8-cp38-none-win_amd64.whl", hash = "sha256:34eec476141a043d478651d1efbf218162cdd57add24dfa659ac89e1a001477a"}, - {file = "orjson-3.9.8-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c9ae634b8a55539c3d5a53813552325733ab3da3601feef8e99f91cef634f3c4"}, - {file = "orjson-3.9.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ad73fde11117b6b103c1d4071168b0e2875d890556fa8597663a5eca81bb812"}, - {file = "orjson-3.9.8-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:154f048e4da06275c1f173445dfbd88f038d29f7529a0dae6157293241b7f5bd"}, - {file = "orjson-3.9.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:428fec9497d17ebb5936495bbeaf12b5952bff5f6fde8a0e64030887b8d8cf94"}, - {file = "orjson-3.9.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ae6509f078eb90d157da7717f2826e55ef08756bc4f5b89448c6b56be4ff2c"}, - {file = "orjson-3.9.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e26836a11b88f839b6902f92e8dd997c32f49486119a1aa67d714bc288aae172"}, - {file = "orjson-3.9.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a27e5161b1f23fd1b5e549b38018bbc7a0f0bd3699d3dec04e2e62d271480d3"}, - {file = "orjson-3.9.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c836845177d6ee92682d0d9b61346a06b140b5666319905a5b423ebb0ecc5d3"}, - {file = "orjson-3.9.8-cp39-none-win32.whl", hash = "sha256:ca4f3e15517bdcdb573dfe6c97d4171247ce50ec82e3a7b708941b53d5f4bc29"}, - {file = "orjson-3.9.8-cp39-none-win_amd64.whl", hash = "sha256:52c0480d5be12697b10b4d748b86acd4999f47e1d8e44e49486d0a550f30fcba"}, - {file = "orjson-3.9.8.tar.gz", hash = "sha256:ed1adc6db9841974170a5195b827ee4e392b1e8ca385b19fcdc3248489844059"}, + {file = "orjson-3.9.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6b4e2bed7d00753c438e83b613923afdd067564ff7ed696bfe3a7b073a236e07"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd1b8ec63f0bf54a50b498eedeccdca23bd7b658f81c524d18e410c203189365"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab8add018a53665042a5ae68200f1ad14c7953fa12110d12d41166f111724656"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12756a108875526b76e505afe6d6ba34960ac6b8c5ec2f35faf73ef161e97e07"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:890e7519c0c70296253660455f77e3a194554a3c45e42aa193cdebc76a02d82b"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d664880d7f016efbae97c725b243b33c2cbb4851ddc77f683fd1eec4a7894146"}, + {file = "orjson-3.9.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cfdaede0fa5b500314ec7b1249c7e30e871504a57004acd116be6acdda3b8ab3"}, + {file = "orjson-3.9.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6492ff5953011e1ba9ed1bf086835fd574bd0a3cbe252db8e15ed72a30479081"}, + {file = "orjson-3.9.12-cp310-none-win32.whl", hash = "sha256:29bf08e2eadb2c480fdc2e2daae58f2f013dff5d3b506edd1e02963b9ce9f8a9"}, + {file = "orjson-3.9.12-cp310-none-win_amd64.whl", hash = "sha256:0fc156fba60d6b50743337ba09f052d8afc8b64595112996d22f5fce01ab57da"}, + {file = "orjson-3.9.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2849f88a0a12b8d94579b67486cbd8f3a49e36a4cb3d3f0ab352c596078c730c"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3186b18754befa660b31c649a108a915493ea69b4fc33f624ed854ad3563ac65"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbbf313c9fb9d4f6cf9c22ced4b6682230457741daeb3d7060c5d06c2e73884a"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99e8cd005b3926c3db9b63d264bd05e1bf4451787cc79a048f27f5190a9a0311"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59feb148392d9155f3bfed0a2a3209268e000c2c3c834fb8fe1a6af9392efcbf"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4ae815a172a1f073b05b9e04273e3b23e608a0858c4e76f606d2d75fcabde0c"}, + {file = "orjson-3.9.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed398f9a9d5a1bf55b6e362ffc80ac846af2122d14a8243a1e6510a4eabcb71e"}, + {file = "orjson-3.9.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d3cfb76600c5a1e6be91326b8f3b83035a370e727854a96d801c1ea08b708073"}, + {file = "orjson-3.9.12-cp311-none-win32.whl", hash = "sha256:a2b6f5252c92bcab3b742ddb3ac195c0fa74bed4319acd74f5d54d79ef4715dc"}, + {file = "orjson-3.9.12-cp311-none-win_amd64.whl", hash = "sha256:c95488e4aa1d078ff5776b58f66bd29d628fa59adcb2047f4efd3ecb2bd41a71"}, + {file = "orjson-3.9.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6ce2062c4af43b92b0221ed4f445632c6bf4213f8a7da5396a122931377acd9"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:950951799967558c214cd6cceb7ceceed6f81d2c3c4135ee4a2c9c69f58aa225"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2dfaf71499d6fd4153f5c86eebb68e3ec1bf95851b030a4b55c7637a37bbdee4"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:659a8d7279e46c97661839035a1a218b61957316bf0202674e944ac5cfe7ed83"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af17fa87bccad0b7f6fd8ac8f9cbc9ee656b4552783b10b97a071337616db3e4"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd52dec9eddf4c8c74392f3fd52fa137b5f2e2bed1d9ae958d879de5f7d7cded"}, + {file = "orjson-3.9.12-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:640e2b5d8e36b970202cfd0799d11a9a4ab46cf9212332cd642101ec952df7c8"}, + {file = "orjson-3.9.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:daa438bd8024e03bcea2c5a92cd719a663a58e223fba967296b6ab9992259dbf"}, + {file = "orjson-3.9.12-cp312-none-win_amd64.whl", hash = "sha256:1bb8f657c39ecdb924d02e809f992c9aafeb1ad70127d53fb573a6a6ab59d549"}, + {file = "orjson-3.9.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f4098c7674901402c86ba6045a551a2ee345f9f7ed54eeffc7d86d155c8427e5"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5586a533998267458fad3a457d6f3cdbddbcce696c916599fa8e2a10a89b24d3"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54071b7398cd3f90e4bb61df46705ee96cb5e33e53fc0b2f47dbd9b000e238e1"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:67426651faa671b40443ea6f03065f9c8e22272b62fa23238b3efdacd301df31"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a0cd56e8ee56b203abae7d482ac0d233dbfb436bb2e2d5cbcb539fe1200a312"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a84a0c3d4841a42e2571b1c1ead20a83e2792644c5827a606c50fc8af7ca4bee"}, + {file = "orjson-3.9.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:09d60450cda3fa6c8ed17770c3a88473a16460cd0ff2ba74ef0df663b6fd3bb8"}, + {file = "orjson-3.9.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bc82a4db9934a78ade211cf2e07161e4f068a461c1796465d10069cb50b32a80"}, + {file = "orjson-3.9.12-cp38-none-win32.whl", hash = "sha256:61563d5d3b0019804d782137a4f32c72dc44c84e7d078b89d2d2a1adbaa47b52"}, + {file = "orjson-3.9.12-cp38-none-win_amd64.whl", hash = "sha256:410f24309fbbaa2fab776e3212a81b96a1ec6037259359a32ea79fbccfcf76aa"}, + {file = "orjson-3.9.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e773f251258dd82795fd5daeac081d00b97bacf1548e44e71245543374874bcf"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b159baecfda51c840a619948c25817d37733a4d9877fea96590ef8606468b362"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:975e72e81a249174840d5a8df977d067b0183ef1560a32998be340f7e195c730"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06e42e899dde61eb1851a9fad7f1a21b8e4be063438399b63c07839b57668f6c"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c157e999e5694475a5515942aebeed6e43f7a1ed52267c1c93dcfde7d78d421"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dde1bc7c035f2d03aa49dc8642d9c6c9b1a81f2470e02055e76ed8853cfae0c3"}, + {file = "orjson-3.9.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b0e9d73cdbdad76a53a48f563447e0e1ce34bcecef4614eb4b146383e6e7d8c9"}, + {file = "orjson-3.9.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:96e44b21fe407b8ed48afbb3721f3c8c8ce17e345fbe232bd4651ace7317782d"}, + {file = "orjson-3.9.12-cp39-none-win32.whl", hash = "sha256:cbd0f3555205bf2a60f8812133f2452d498dbefa14423ba90fe89f32276f7abf"}, + {file = "orjson-3.9.12-cp39-none-win_amd64.whl", hash = "sha256:03ea7ee7e992532c2f4a06edd7ee1553f0644790553a118e003e3c405add41fa"}, + {file = "orjson-3.9.12.tar.gz", hash = "sha256:da908d23a3b3243632b523344403b128722a5f45e278a8343c2bb67538dff0e4"}, ] [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2521,78 +2471,80 @@ files = [ [[package]] name = "pandas" -version = "2.1.1" +version = "2.2.0" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"}, - {file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"}, - {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"}, - {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"}, - {file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"}, - {file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"}, - {file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"}, - {file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"}, - {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"}, - {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"}, - {file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"}, - {file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"}, - {file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"}, - {file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"}, - {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"}, - {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"}, - {file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"}, - {file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"}, - {file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"}, - {file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"}, - {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"}, - {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"}, - {file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"}, - {file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"}, - {file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"}, + {file = "pandas-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670"}, + {file = "pandas-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a"}, + {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18"}, + {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e"}, + {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5"}, + {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1"}, + {file = "pandas-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430"}, + {file = "pandas-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5"}, + {file = "pandas-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b"}, + {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f"}, + {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88"}, + {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3"}, + {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71"}, + {file = "pandas-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9"}, + {file = "pandas-2.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc"}, + {file = "pandas-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1"}, + {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440"}, + {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106"}, + {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e"}, + {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042"}, + {file = "pandas-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30"}, + {file = "pandas-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d"}, + {file = "pandas-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab"}, + {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a"}, + {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a"}, + {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd"}, + {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7"}, + {file = "pandas-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae"}, + {file = "pandas-2.2.0.tar.gz", hash = "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2"}, ] [package.dependencies] numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" -tzdata = ">=2022.1" +tzdata = ">=2022.7" [package.extras] -all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] -aws = ["s3fs (>=2022.05.0)"] -clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] -compression = ["zstandard (>=0.17.0)"] -computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2022.05.0)"] -gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] -hdf5 = ["tables (>=3.7.0)"] -html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] -mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] -spss = ["pyreadstat (>=1.1.5)"] -sql-other = ["SQLAlchemy (>=1.4.36)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.8.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] [[package]] name = "parsimonious" version = "0.9.0" description = "(Soon to be) the fastest pure-Python PEG parser I could muster" -category = "main" optional = false python-versions = "*" files = [ @@ -2604,21 +2556,19 @@ regex = ">=2022.3.15" [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "pgpy" version = "0.6.0" description = "Pretty Good Privacy for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2631,82 +2581,98 @@ pyasn1 = "*" [[package]] name = "pillow" -version = "10.0.1" +version = "10.2.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, - {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, - {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, - {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, - {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, - {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, - {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, - {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "3.11.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -2715,14 +2681,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -2731,14 +2696,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.4.0" +version = "3.6.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, - {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, + {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, + {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, ] [package.dependencies] @@ -2750,32 +2714,28 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "4.24.4" +version = "4.25.2" description = "" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, - {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, - {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, - {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, - {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, - {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, - {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, - {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, - {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, - {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, - {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, + {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, + {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, + {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, + {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, + {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, + {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, + {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, + {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, + {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, ] [[package]] name = "psycopg2" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2794,21 +2754,19 @@ files = [ [[package]] name = "pyasn1" -version = "0.5.0" +version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, - {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] [[package]] name = "pycocotools" version = "2.0.7" description = "Official APIs for the MS-COCO dataset" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2842,7 +2800,6 @@ numpy = "*" name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2852,90 +2809,88 @@ files = [ [[package]] name = "pycryptodome" -version = "3.19.0" +version = "3.20.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"}, - {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] [[package]] name = "pydantic" -version = "1.10.13" +version = "1.10.14" description = "Data validation and settings management using python type hints" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, + {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, + {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, + {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, + {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, + {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, + {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, + {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, + {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, ] [package.dependencies] @@ -2949,7 +2904,6 @@ email = ["email-validator (>=1.0.3)"] name = "pyparsing" version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -2962,14 +2916,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -2987,7 +2940,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3000,14 +2952,13 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -3015,32 +2966,29 @@ cli = ["click (>=5.0)"] [[package]] name = "pytz" -version = "2023.3.post1" +version = "2023.4" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, + {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, ] [[package]] name = "pyunormalize" -version = "15.0.0" +version = "15.1.0" description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pyunormalize-15.0.0.tar.gz", hash = "sha256:e63fdba0d85ea04579dde2fc29a072dba773dcae600b04faf6cc90714c8b1302"}, + {file = "pyunormalize-15.1.0.tar.gz", hash = "sha256:cf4a87451a0f1cb76911aa97f432f4579e1f564a2f0c84ce488c73a73901b6c1"}, ] [[package]] name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "main" optional = false python-versions = "*" files = [ @@ -3064,7 +3012,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3073,6 +3020,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3080,8 +3028,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3098,6 +3053,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3105,6 +3061,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3112,14 +3069,13 @@ files = [ [[package]] name = "referencing" -version = "0.30.2" +version = "0.33.0" description = "JSON Referencing + Python" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, - {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, ] [package.dependencies] @@ -3128,107 +3084,110 @@ rpds-py = ">=0.7.0" [[package]] name = "regex" -version = "2023.10.3" +version = "2023.12.25" description = "Alternative regular expression module, to replace re." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3248,194 +3207,183 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rlp" -version = "3.0.0" -description = "A package for Recursive Length Prefix encoding and decoding" -category = "main" +version = "4.0.0" +description = "rlp: A package for Recursive Length Prefix encoding and decoding" optional = false -python-versions = "*" +python-versions = ">=3.8, <4" files = [ - {file = "rlp-3.0.0-py2.py3-none-any.whl", hash = "sha256:d2a963225b3f26795c5b52310e0871df9824af56823d739511583ef459895a7d"}, - {file = "rlp-3.0.0.tar.gz", hash = "sha256:63b0465d2948cd9f01de449d7adfb92d207c1aef3982f20310f8009be4a507e8"}, + {file = "rlp-4.0.0-py3-none-any.whl", hash = "sha256:1747fd933e054e6d25abfe591be92e19a4193a56c93981c05bd0f84dfe279f14"}, + {file = "rlp-4.0.0.tar.gz", hash = "sha256:61a5541f86e4684ab145cb849a5929d2ced8222930a570b3941cf4af16b72a78"}, ] [package.dependencies] -eth-utils = ">=2.0.0,<3" +eth-utils = ">=2" [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.4.1)", "hypothesis (==5.19.0)", "ipython", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] -lint = ["flake8 (==3.4.1)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] rust-backend = ["rusty-rlp (>=0.2.1,<0.3)"] -test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] +test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "rpds-py" -version = "0.10.4" +version = "0.17.1" description = "Python bindings to Rust's persistent data structures (rpds)" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e41824343c2c129599645373992b1ce17720bb8a514f04ff9567031e1c26951e"}, - {file = "rpds_py-0.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b9d8884d58ea8801e5906a491ab34af975091af76d1a389173db491ee7e316bb"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db93f9017b384a4f194e1d89e1ce82d0a41b1fafdbbd3e0c8912baf13f2950f"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c31ecfc53ac03dad4928a1712f3a2893008bfba1b3cde49e1c14ff67faae2290"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f92d2372ec992c82fd7c74aa21e2a1910b3dcdc6a7e6392919a138f21d528a3"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7ea49ddf51d5ec0c3cbd95190dd15e077a3153c8d4b22a33da43b5dd2b3c640"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c27942722cd5039bbf5098c7e21935a96243fed00ea11a9589f3c6c6424bd84"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08f07150c8ebbdbce1d2d51b8e9f4d588749a2af6a98035485ebe45c7ad9394e"}, - {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f3331a3684192659fa1090bf2b448db928152fcba08222e58106f44758ef25f7"}, - {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:efffa359cc69840c8793f0c05a7b663de6afa7b9078fa6c80309ee38b9db677d"}, - {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86e8d6ff15fa7a9590c0addaf3ce52fb58bda4299cab2c2d0afa404db6848dab"}, - {file = "rpds_py-0.10.4-cp310-none-win32.whl", hash = "sha256:8f90fc6dd505867514c8b8ef68a712dc0be90031a773c1ae2ad469f04062daef"}, - {file = "rpds_py-0.10.4-cp310-none-win_amd64.whl", hash = "sha256:9f9184744fb800c9f28e155a5896ecb54816296ee79d5d1978be6a2ae60f53c4"}, - {file = "rpds_py-0.10.4-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:72e9b1e92830c876cd49565d8404e4dcc9928302d348ea2517bc3f9e3a873a2a"}, - {file = "rpds_py-0.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3650eae998dc718960e90120eb45d42bd57b18b21b10cb9ee05f91bff2345d48"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f40413d2859737ce6d95c29ce2dde0ef7cdc3063b5830ae4342fef5922c3bba7"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b953d11b544ca5f2705bb77b177d8e17ab1bfd69e0fd99790a11549d2302258c"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28b4942ec7d9d6114c1e08cace0157db92ef674636a38093cab779ace5742d3a"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e0e2e01c5f61ddf47e3ed2d1fe1c9136e780ca6222d57a2517b9b02afd4710c"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:927e3461dae0c09b1f2e0066e50c1a9204f8a64a3060f596e9a6742d3b307785"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e69bbe0ede8f7fe2616e779421bbdb37f025c802335a90f6416e4d98b368a37"}, - {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc688a59c100f038fa9fec9e4ab457c2e2d1fca350fe7ea395016666f0d0a2dc"}, - {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ec001689402b9104700b50a005c2d3d0218eae90eaa8bdbbd776fe78fe8a74b7"}, - {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628fbb8be71a103499d10b189af7764996ab2634ed7b44b423f1e19901606e0e"}, - {file = "rpds_py-0.10.4-cp311-none-win32.whl", hash = "sha256:e3f9c9e5dd8eba4768e15f19044e1b5e216929a43a54b4ab329e103aed9f3eda"}, - {file = "rpds_py-0.10.4-cp311-none-win_amd64.whl", hash = "sha256:3bc561c183684636c0099f9c3fbab8c1671841942edbce784bb01b4707d17924"}, - {file = "rpds_py-0.10.4-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:36ff30385fb9fb3ac23a28bffdd4a230a5229ed5b15704b708b7c84bfb7fce51"}, - {file = "rpds_py-0.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db0589e0bf41ff6ce284ab045ca89f27be1adf19e7bce26c2e7de6739a70c18b"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c330cb125983c5d380fef4a4155248a276297c86d64625fdaf500157e1981c"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d230fddc60caced271cc038e43e6fb8f4dd6b2dbaa44ac9763f2d76d05b0365a"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a9e864ec051a58fdb6bb2e6da03942adb20273897bc70067aee283e62bbac4d"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e41d5b334e8de4bc3f38843f31b2afa9a0c472ebf73119d3fd55cde08974bdf"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bb3f3cb6072c73e6ec1f865d8b80419b599f1597acf33f63fbf02252aab5a03"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:576d48e1e45c211e99fc02655ade65c32a75d3e383ccfd98ce59cece133ed02c"}, - {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b28b9668a22ca2cfca4433441ba9acb2899624a323787a509a3dc5fbfa79c49d"}, - {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ddbd113a37307638f94be5ae232a325155fd24dbfae2c56455da8724b471e7be"}, - {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd0ad98c7d72b0e4cbfe89cdfa12cd07d2fd6ed22864341cdce12b318a383442"}, - {file = "rpds_py-0.10.4-cp312-none-win32.whl", hash = "sha256:2a97406d5e08b7095428f01dac0d3c091dc072351151945a167e7968d2755559"}, - {file = "rpds_py-0.10.4-cp312-none-win_amd64.whl", hash = "sha256:aab24b9bbaa3d49e666e9309556591aa00748bd24ea74257a405f7fed9e8b10d"}, - {file = "rpds_py-0.10.4-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6c5ca3eb817fb54bfd066740b64a2b31536eb8fe0b183dc35b09a7bd628ed680"}, - {file = "rpds_py-0.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd37ab9a24021821b715478357af1cf369d5a42ac7405e83e5822be00732f463"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2573ec23ad3a59dd2bc622befac845695972f3f2d08dc1a4405d017d20a6c225"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:362faeae52dc6ccc50c0b6a01fa2ec0830bb61c292033f3749a46040b876f4ba"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f6e53461b19ddbb3354fe5bcf3d50d4333604ae4bf25b478333d83ca68002c"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6090ba604ea06b525a231450ae5d343917a393cbf50423900dea968daf61d16f"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e29dac59df890972f73c511948072897f512974714a803fe793635b80ff8c7"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f82abb5c5b83dc30e96be99ce76239a030b62a73a13c64410e429660a5602bfd"}, - {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3628815fd170a64624001bfb4e28946fd515bd672e68a1902d9e0290186eaf3"}, - {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d37f27ad80f742ef82796af3fe091888864958ad0bc8bab03da1830fa00c6004"}, - {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:255a23bded80605e9f3997753e3a4b89c9aec9efb07ec036b1ca81440efcc1a9"}, - {file = "rpds_py-0.10.4-cp38-none-win32.whl", hash = "sha256:049098dabfe705e9638c55a3321137a821399c50940041a6fcce267a22c70db2"}, - {file = "rpds_py-0.10.4-cp38-none-win_amd64.whl", hash = "sha256:aa45cc71bf23a3181b8aa62466b5a2b7b7fb90fdc01df67ca433cd4fce7ec94d"}, - {file = "rpds_py-0.10.4-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:3507c459767cf24c11e9520e2a37c89674266abe8e65453e5cb66398aa47ee7b"}, - {file = "rpds_py-0.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2603e084054351cc65097da326570102c4c5bd07426ba8471ceaefdb0b642cc9"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0f1d336786cb62613c72c00578c98e5bb8cd57b49c5bae5d4ab906ca7872f98"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf032367f921201deaecf221d4cc895ea84b3decf50a9c73ee106f961885a0ad"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f050ceffd8c730c1619a16bbf0b9cd037dcdb94b54710928ba38c7bde67e4a4"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8709eb4ab477c533b7d0a76cd3065d7d95c9e25e6b9f6e27caeeb8c63e8799c9"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc20dadb102140dff63529e08ce6f9745dbd36e673ebb2b1c4a63e134bca81c2"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd7da2adc721ccf19ac7ec86cae3a4fcaba03d9c477d5bd64ded6e9bb817bf3f"}, - {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5dba1c11e089b526379e74f6c636202e4c5bad9a48c7416502b8a5b0d026c91"}, - {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ffd539d213c1ea2989ab92a5b9371ae7159c8c03cf2bcb9f2f594752f755ecd3"}, - {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e791e3d13b14d0a7921804d0efe4d7bd15508bbcf8cb7a0c1ee1a27319a5f033"}, - {file = "rpds_py-0.10.4-cp39-none-win32.whl", hash = "sha256:2f2ac8bb01f705c5caaa7fe77ffd9b03f92f1b5061b94228f6ea5eaa0fca68ad"}, - {file = "rpds_py-0.10.4-cp39-none-win_amd64.whl", hash = "sha256:7c7ca791bedda059e5195cf7c6b77384657a51429357cdd23e64ac1d4973d6dc"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:9c7e7bd1fa1f535af71dfcd3700fc83a6dc261a1204f8f5327d8ffe82e52905d"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7089d8bfa8064b28b2e39f5af7bf12d42f61caed884e35b9b4ea9e6fb1175077"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1f191befea279cb9669b57be97ab1785781c8bab805900e95742ebfaa9cbf1d"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98c0aecf661c175ce9cb17347fc51a5c98c3e9189ca57e8fcd9348dae18541db"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81359911c3bb31c899c6a5c23b403bdc0279215e5b3bc0d2a692489fed38632"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83da147124499fe41ed86edf34b4e81e951b3fe28edcc46288aac24e8a5c8484"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49db6c0a0e6626c2b97f5e7f8f7074da21cbd8ec73340c25e839a2457c007efa"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:125776d5db15162fdd9135372bef7fe4fb7c5f5810cf25898eb74a06a0816aec"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:32819b662e3b4c26355a4403ea2f60c0a00db45b640fe722dd12db3d2ef807fb"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3bd38b80491ef9686f719c1ad3d24d14fbd0e069988fdd4e7d1a6ffcdd7f4a13"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2e79eeeff8394284b09577f36316d410525e0cf0133abb3de10660e704d3d38e"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3e37f1f134037601eb4b1f46854194f0cc082435dac2ee3de11e51529f7831f2"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ba3246c60303eab3d0e562addf25a983d60bddc36f4d1edc2510f056d19df255"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9123ba0f3f98ff79780eebca9984a2b525f88563844b740f94cffb9099701230"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d98802b78093c7083cc51f83da41a5be5a57d406798c9f69424bd75f8ae0812a"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58bae860d1d116e6b4e1aad0cdc48a187d5893994f56d26db0c5534df7a47afd"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd7e62e7d5bcfa38a62d8397fba6d0428b970ab7954c2197501cd1624f7f0bbb"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83f5228459b84fa6279e4126a53abfdd73cd9cc183947ee5084153880f65d7"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bcb1abecd998a72ad4e36a0fca93577fd0c059a6aacc44f16247031b98f6ff4"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9e7b3ad9f53ea9e085b3d27286dd13f8290969c0a153f8a52c8b5c46002c374b"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:cbec8e43cace64e63398155dc585dc479a89fef1e57ead06c22d3441e1bd09c3"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ad21c60fc880204798f320387164dcacc25818a7b4ec2a0bf6b6c1d57b007d23"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:6baea8a4f6f01e69e75cfdef3edd4a4d1c4b56238febbdf123ce96d09fbff010"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:94876c21512535955a960f42a155213315e6ab06a4ce8ce372341a2a1b143eeb"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cb55454a20d1b935f9eaab52e6ceab624a2efd8b52927c7ae7a43e02828dbe0"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13cbd79ccedc6b39c279af31ebfb0aec0467ad5d14641ddb15738bf6e4146157"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00a88003db3cc953f8656b59fc9af9d0637a1fb93c235814007988f8c153b2f2"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0f7f77a77c37159c9f417b8dd847f67a29e98c6acb52ee98fc6b91efbd1b2b6"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70563a1596d2e0660ca2cebb738443437fc0e38597e7cbb276de0a7363924a52"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3ece9aa6d07e18c966f14b4352a4c6f40249f6174d3d2c694c1062e19c6adbb"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d5ad7b1a1f6964d19b1a8acfc14bf7864f39587b3e25c16ca04f6cd1815026b3"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:60018626e637528a1fa64bb3a2b3e46ab7bf672052316d61c3629814d5e65052"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ae8a32ab77a84cc870bbfb60645851ca0f7d58fd251085ad67464b1445d632ca"}, - {file = "rpds_py-0.10.4.tar.gz", hash = "sha256:18d5ff7fbd305a1d564273e9eb22de83ae3cd9cd6329fddc8f12f6428a711a6a"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] [[package]] name = "ruamel-yaml" -version = "0.17.35" +version = "0.18.5" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -category = "main" optional = false -python-versions = ">=3" +python-versions = ">=3.7" files = [ - {file = "ruamel.yaml-0.17.35-py3-none-any.whl", hash = "sha256:b105e3e6fc15b41fdb201ba1b95162ae566a4ef792b9f884c46b4ccc5513a87a"}, - {file = "ruamel.yaml-0.17.35.tar.gz", hash = "sha256:801046a9caacb1b43acc118969b49b96b65e8847f29029563b29ac61d02db61b"}, + {file = "ruamel.yaml-0.18.5-py3-none-any.whl", hash = "sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada"}, + {file = "ruamel.yaml-0.18.5.tar.gz", hash = "sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} [package.extras] -docs = ["ryd"] +docs = ["mercurial (>5.7)", "ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" version = "0.2.8" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -category = "main" optional = false python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, @@ -3444,110 +3392,83 @@ files = [ [[package]] name = "s3transfer" -version = "0.7.0" +version = "0.10.0" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"}, - {file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"}, + {file = "s3transfer-0.10.0-py3-none-any.whl", hash = "sha256:3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e"}, + {file = "s3transfer-0.10.0.tar.gz", hash = "sha256:d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b"}, ] [package.dependencies] -botocore = ">=1.12.36,<2.0a.0" +botocore = ">=1.33.2,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "scipy" -version = "1.11.3" +version = "1.12.0" description = "Fundamental algorithms for scientific computing in Python" -category = "main" optional = false -python-versions = "<3.13,>=3.9" +python-versions = ">=3.9" files = [ - {file = "scipy-1.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0"}, - {file = "scipy-1.11.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3"}, - {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc"}, - {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1"}, - {file = "scipy-1.11.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7abda0e62ef00cde826d441485e2e32fe737bdddee3324e35c0e01dee65e2a88"}, - {file = "scipy-1.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929"}, - {file = "scipy-1.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165"}, - {file = "scipy-1.11.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a"}, - {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3"}, - {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221"}, - {file = "scipy-1.11.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:91770cb3b1e81ae19463b3c235bf1e0e330767dca9eb4cd73ba3ded6c4151e4d"}, - {file = "scipy-1.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820"}, - {file = "scipy-1.11.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15"}, - {file = "scipy-1.11.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7"}, - {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc"}, - {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6"}, - {file = "scipy-1.11.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:74e89dc5e00201e71dd94f5f382ab1c6a9f3ff806c7d24e4e90928bb1aafb280"}, - {file = "scipy-1.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6"}, - {file = "scipy-1.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83"}, - {file = "scipy-1.11.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e"}, - {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917"}, - {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156"}, - {file = "scipy-1.11.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15f237e890c24aef6891c7d008f9ff7e758c6ef39a2b5df264650eb7900403c0"}, - {file = "scipy-1.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2"}, - {file = "scipy-1.11.3.tar.gz", hash = "sha256:bba4d955f54edd61899776bad459bf7326e14b9fa1c552181f0479cc60a568cd"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, + {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, + {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, + {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, + {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, + {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, + {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, + {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, + {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, + {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, ] [package.dependencies] -numpy = ">=1.21.6,<1.28.0" +numpy = ">=1.22.4,<1.29.0" [package.extras] dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" -version = "68.2.2" +version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] -[[package]] -name = "setuptools-scm" -version = "8.0.4" -description = "the blessed package to manage your versions by scm tags" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, - {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, -] - -[package.dependencies] -packaging = ">=20" -setuptools = "*" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} -typing-extensions = "*" - -[package.extras] -docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] -rich = ["rich"] -test = ["build", "pytest", "rich", "wheel"] - [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3559,7 +3480,6 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3569,70 +3489,70 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.21" +version = "2.0.25" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:56628ca27aa17b5890391ded4e385bf0480209726f198799b7e980c6bd473bd7"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db726be58837fe5ac39859e0fa40baafe54c6d54c02aba1d47d25536170b690f"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7421c1bfdbb7214313919472307be650bd45c4dc2fcb317d64d078993de045b"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:632784f7a6f12cfa0e84bf2a5003b07660addccf5563c132cd23b7cc1d7371a9"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f6f7276cf26145a888f2182a98f204541b519d9ea358a65d82095d9c9e22f917"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2a1f7ffac934bc0ea717fa1596f938483fb8c402233f9b26679b4f7b38d6ab6e"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-win32.whl", hash = "sha256:bfece2f7cec502ec5f759bbc09ce711445372deeac3628f6fa1c16b7fb45b682"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-win_amd64.whl", hash = "sha256:526b869a0f4f000d8d8ee3409d0becca30ae73f494cbb48801da0129601f72c6"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"}, - {file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"}, - {file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, + {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, + {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, ] [package.dependencies] greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.2.0" +typing-extensions = ">=4.6.0" [package.extras] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] @@ -3642,7 +3562,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] +oracle = ["cx_oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -3652,13 +3572,12 @@ postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlalchemy-utils" version = "0.41.1" description = "Various utility functions for SQLAlchemy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3687,7 +3606,6 @@ url = ["furl (>=0.4.1)"] name = "starlette" version = "0.27.0" description = "The little ASGI library that shines." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3705,7 +3623,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam name = "tensorboardx" version = "2.6.2.2" description = "TensorBoardX lets you watch Tensors Flow without Tensorflow" -category = "main" optional = false python-versions = "*" files = [ @@ -3722,7 +3639,6 @@ protobuf = ">=3.20" name = "tinydb" version = "4.8.0" description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" -category = "main" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -3734,7 +3650,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3744,21 +3659,19 @@ files = [ [[package]] name = "toolz" -version = "0.12.0" +version = "0.12.1" description = "List processing tools and functional utilities" -category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, - {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, ] [[package]] name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3779,7 +3692,6 @@ telegram = ["requests"] name = "tuspy" version = "0.2.5" description = "A Python client for the tus resumable upload protocol -> http://tus.io" -category = "main" optional = false python-versions = "*" files = [ @@ -3800,51 +3712,47 @@ test = ["coverage (>=4.2)", "mock (>=2.0.0)", "pytest (>=3.0.3)", "pytest-cov (> [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "tzdata" -version = "2023.3" +version = "2023.4" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, ] [[package]] name = "tzlocal" -version = "5.1" +version = "5.2" description = "tzinfo object for the local timezone" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tzlocal-5.1-py3-none-any.whl", hash = "sha256:2938498395d5f6a898ab8009555cb37a4d360913ad375d4747ef16826b03ef23"}, - {file = "tzlocal-5.1.tar.gz", hash = "sha256:a5ccb2365b295ed964e0a98ad076fe10c495591e75505d34f154d60a7f1ed722"}, + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, ] [package.dependencies] tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] [[package]] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3862,7 +3770,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "uvicorn" version = "0.22.0" description = "The lightning-fast ASGI server." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3881,7 +3788,6 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", name = "validators" version = "0.20.0" description = "Python Data Validation for Humansâ„¢." -category = "main" optional = false python-versions = ">=3.4" files = [ @@ -3896,20 +3802,19 @@ test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"] [[package]] name = "virtualenv" -version = "20.24.5" +version = "20.25.0" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, - {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] @@ -3919,7 +3824,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "web3" version = "6.8.0" description = "web3.py" -category = "main" optional = false python-versions = ">=3.7.2" files = [ @@ -3953,89 +3857,89 @@ tester = ["eth-tester[py-evm] (==v0.9.1-b.1)", "py-geth (>=3.11.0)"] [[package]] name = "websockets" -version = "11.0.3" +version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, - {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, - {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, - {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, - {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, - {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, - {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, - {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, - {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, - {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, - {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, - {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, - {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, - {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, ] [[package]] name = "xmltodict" version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" -category = "main" optional = false python-versions = ">=3.4" files = [ @@ -4045,86 +3949,101 @@ files = [ [[package]] name = "yarl" -version = "1.9.2" +version = "1.9.4" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, ] [package.dependencies] @@ -4134,4 +4053,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.13" -content-hash = "c08645d76cfdc6df814f2a35b16365ce13ef292b8fcabd9929cc8d884eea83d1" +content-hash = "e35de373388feda7314836a75cf89eec394906188c027b11db1d21d96a623717" diff --git a/packages/examples/cvat/exchange-oracle/pyproject.toml b/packages/examples/cvat/exchange-oracle/pyproject.toml index 852bb6d102..ee65c380ef 100644 --- a/packages/examples/cvat/exchange-oracle/pyproject.toml +++ b/packages/examples/cvat/exchange-oracle/pyproject.toml @@ -19,7 +19,7 @@ pytest = "^7.2.2" cvat-sdk = "2.6.0" sqlalchemy = "^2.0.16" apscheduler = "^3.10.1" -human-protocol-sdk = "^1.1.15" +human-protocol-sdk = "^1.1.19" xmltodict = "^0.13.0" datumaro = {git = "https://github.com/cvat-ai/datumaro.git", rev = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11"} boto3 = "^1.28.33" diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 4c9f13944d..ec32964476 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -1,13 +1,15 @@ import json from typing import List -from human_protocol_sdk.constants import Status +from human_protocol_sdk.constants import ChainId, Status from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient +from src.utils.cloud_storage import parse_bucket_url + def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: - escrow = EscrowUtils.get_escrow(chain_id, escrow_address.lower()) + escrow = EscrowUtils.get_escrow(ChainId(chain_id), escrow_address) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") @@ -40,7 +42,22 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - manifest_content = StorageClient.download_file_from_url(escrow.manifestUrl) + + parsed_url = parse_bucket_url(escrow.manifest_url) + + secure = False + if parsed_url.host_url.startswith("https://"): + host = parsed_url.host_url[len("https://") :] + secure = True + elif parsed_url.host_url.startswith("http://"): + host = parsed_url.host_url[len("http://") :] + else: + host = parsed_url.host_url + + manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( + [parsed_url.path], bucket=parsed_url.bucket_name + )[0] + return json.loads(manifest_content.decode("utf-8")) @@ -49,4 +66,4 @@ def get_job_launcher_address(chain_id: int, escrow_address: str) -> str: def get_recording_oracle_address(chain_id: int, escrow_address: str) -> str: - return get_escrow(chain_id, escrow_address).recordingOracle + return get_escrow(chain_id, escrow_address).recording_oracle diff --git a/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py b/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py index 7f86a4904b..a41e0a98c8 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/kvstore.py @@ -1,7 +1,7 @@ -from human_protocol_sdk.staking import StakingClient +from human_protocol_sdk.constants import ChainId +from human_protocol_sdk.staking import StakingUtils from src.chain.escrow import get_escrow -from src.chain.web3 import get_web3 from src.core.config import Config @@ -11,9 +11,7 @@ def get_recording_oracle_url(chain_id: int, escrow_address: str) -> str: escrow = get_escrow(chain_id, escrow_address) - web3 = get_web3(chain_id) - staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.recordingOracle)["webhook_url"] + return StakingUtils.get_leader(ChainId(chain_id), escrow.recording_oracle).webhook_url def get_job_launcher_url(chain_id: int, escrow_address: str) -> str: @@ -22,6 +20,4 @@ def get_job_launcher_url(chain_id: int, escrow_address: str) -> str: escrow = get_escrow(chain_id, escrow_address) - web3 = get_web3(chain_id) - staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.launcher)["webhook_url"] + return StakingUtils.get_leader(ChainId(chain_id), escrow.launcher).webhook_url diff --git a/packages/examples/cvat/recording-oracle/poetry.lock b/packages/examples/cvat/recording-oracle/poetry.lock index a2f70308bd..b67b55a84c 100644 --- a/packages/examples/cvat/recording-oracle/poetry.lock +++ b/packages/examples/cvat/recording-oracle/poetry.lock @@ -1,119 +1,105 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" -version = "3.8.6" +version = "3.9.3" description = "Async http client/server framework (asyncio)" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -126,14 +112,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alembic" -version = "1.12.0" +version = "1.13.1" description = "A database migration tool for SQLAlchemy." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "alembic-1.12.0-py3-none-any.whl", hash = "sha256:03226222f1cf943deee6c85d9464261a6c710cd19b4fe867a3ad1f25afda610f"}, - {file = "alembic-1.12.0.tar.gz", hash = "sha256:8e7645c32e4f200675e69f0745415335eb59a3663f5feb487abfa0b30c45888b"}, + {file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"}, + {file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"}, ] [package.dependencies] @@ -142,35 +127,34 @@ SQLAlchemy = ">=1.3.0" typing-extensions = ">=4" [package.extras] -tz = ["python-dateutil"] +tz = ["backports.zoneinfo"] [[package]] name = "anyio" -version = "4.0.0" +version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, - {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "apscheduler" version = "3.10.4" description = "In-process task scheduler with Cron-like capabilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -181,7 +165,7 @@ files = [ [package.dependencies] pytz = "*" six = ">=1.4.0" -tzlocal = ">=2.0,<3.0.0 || >=4.0.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" [package.extras] doc = ["sphinx", "sphinx-rtd-theme"] @@ -195,11 +179,67 @@ tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + [[package]] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -209,185 +249,183 @@ files = [ [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "bitarray" -version = "2.8.2" +version = "2.9.2" description = "efficient arrays of booleans -- C extension" -category = "main" optional = false python-versions = "*" files = [ - {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525eda30469522cd840a11ba866d0616c132f6c4be8966a297d7545e97fcb822"}, - {file = "bitarray-2.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3d9730341c825eb167ca06c9dddf6ad4d1b4e71ea7da73cc8c5139fcb5e14ca"}, - {file = "bitarray-2.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad8f8c39c8df184e346184699783f105755003662f0dbe1233d9d9849650ab5f"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8d08330d250df47088c13683322083afbdfafdc31df205616506d6b9f068f"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f19ccba8a6ddf1382b0fb4fb8d4e1330e4a1b148e5d198f0981ba2a97c3492"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4db2e0f58153a376d9a14873e342d507ca32640640284cddf3c1e74a65929477"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b3c27aeea1752f0c1df1e29115e4b6f0249173d71e53c5f7e2c821706f028b"}, - {file = "bitarray-2.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef23f62b3abd287cf368341540ef2a81c86b48de9d488e182e63fe24ac165538"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6d79fd3c58a4dc71ffd0fc55982a9a2079fe94c76ccff2777092f6107d6a049a"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8528c59d3d3df6618777892b60435022d8917de9ea32933d439c7ffd24437237"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c35bb5fe018fd9c42be3c28e74dc7dcfae471c3c6689679dbd0bd1d6dc0f51b7"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:232e8faa8e624f3eb0552a636ebe745cee00480e0e56ad62f17808d281838f2e"}, - {file = "bitarray-2.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:945e97ad2bbf7885426f39641a735a31fd4ca2e84e4d0cd271d9880372d6eae1"}, - {file = "bitarray-2.8.2-cp310-cp310-win32.whl", hash = "sha256:88c2d427ab1b20f220c1d53171b0691faa8f0a219367d84e859f1001e90ceefc"}, - {file = "bitarray-2.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:f7c5745e0f96c2c16c03c7540dbe26f3b62ddee63059be0a014156933f054024"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a610426251d1340baa4d8b7942d2cbfe6a1e20b92c66817ab582e0d341185ab5"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:599b04b04eb1b5b964a35986bea2bc4381145836fe550cc33c40a796b855b985"}, - {file = "bitarray-2.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9014660472f2080d550157164cc5f9376245a34a0ab877b82b95c1f894af5b28"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:532d63c54159f7e0fb520e2f72ef596493bc43810eaa75fac7a188e898ab593b"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1563f11dd70cb1684cfe841e4cf7f35d4f65769de21d12b72cf773a7932615"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e456150af62ee1f24a0c9976947629bfb80d80b4fbd37aa901cf794db6ba9b0"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc29909e4cef05d5e49f5d77ace1dc49311c7791734a048b690521c76b4b7a0"}, - {file = "bitarray-2.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:608385f07a4b0391d4982d1efb83ad70920cd8ca495a7868e44d2a4511cbf84e"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2baf7ec353fa64917045b3efe26e7c12ce0d7b4d120c3773a612dce54f91585"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2c39d1cb04fc277701de6fe2119cc71facc4aff2ca0414b2e326aec337fa1ab4"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:3caf4ca668854bb23db4b65af0068238677b5791bcc45694bf8990f3e26e85c9"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4bbfe4474d3470c724e283bd1fe8ee9ab3cb6a4c378112926f45d41e326a7622"}, - {file = "bitarray-2.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb941981676dc7859d53199a10a33ca56a3146cce6a45bc6ad70572c1147157d"}, - {file = "bitarray-2.8.2-cp311-cp311-win32.whl", hash = "sha256:e8963d7ac292f41654fa7cbc1a34efdb09e5a42399b2e3689c3fd5b8b4e0fe16"}, - {file = "bitarray-2.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:ee779a291330287b341044635fce2979176d113b0dcce0308dc5d62da7951eec"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:05d84765bbfd0aa10890c765c56c917c237987325c4e327f3c0febbfc34365c8"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c7b7be4bff27d7ce6a81d3993755371b5f5b42436afa151868e8fd599acbab19"}, - {file = "bitarray-2.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c3d51ab9f3d5b9a10295abe480c50bf74ee5bf3d984c4cee77e493e575acc869"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00bad63ef6f9d22ba36b01b89167176a451ea22a916d1dfa77d73e0298f1d1f9"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:225e19d37b234d4d721557434b7d5590cd63b6342492b689e2d694d44d7cc537"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e3ab9870c496e5a058436bf4d96ed111ca6154c8ef8147b70c44c188d6fb2c"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3e182c766cd6f302e99e0d8e44927d533356e9d6ac93fcd09987ebead467aa"}, - {file = "bitarray-2.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7bb559b68eb9cb3c4f867eb9fb39a696c4da70a41fad37b410bd0c7b426a8ce"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:97e658a3793478d6bca684f47f29f62542312683687bc045dc3cb588160e74b3"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:dd351b8fbc77c2e2ebc3eeadc0cf72bd5024a43bef5a847697e2b076d1201636"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:280809e56a7098f48165ce134222098e4cfe7084b10d69bbc31367942e541dfd"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14bc38ced7edffff25ee748c1eabc530624c9af68f86322b030b11b7918b966f"}, - {file = "bitarray-2.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de4953b6b1e19dabd23767bd1f83f1cf73978372189dec0e2dd8b3d6971100d6"}, - {file = "bitarray-2.8.2-cp312-cp312-win32.whl", hash = "sha256:99196b4730d887a4bc578f05039b55dc57b131c81b5a5e03efa619b587bdf293"}, - {file = "bitarray-2.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:215a5bf8fdcbed700cc8782d4044e1f036606d5c321710d83e8da6d0fdfe07d5"}, - {file = "bitarray-2.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e9c54136c9fab2cefe9801e336b8a3aa7299bcfe7f387379cc6394ad1d5a484b"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08ad70c1555d9622cecd8f1b132a5341d183a9161aba93cc9739bbaabe4220b0"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:384be6b7df8fb6a93ddd88d4184094f2ba4f1d07c30dcd4ae164d185d31a2af6"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd2a098250c683d248a6490ac437ed56f7164d2151572231bd26c76bfe111b11"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ae5c18b9a70cb0ae576a8a3c8a9a0659356c016b49cc6b263dd987d344f30d"}, - {file = "bitarray-2.8.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:188f5780f1cfbeba0c3ddb1aa3fa0415ab1a8aa04e9e89f70ad5403197013437"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5f2a96c5b40727bc21a695d3a106f49e88572fa11427bf2193cabd99e624c901"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b6df948da34b5fb949698092573d798c76c54f2f2188db59276d599075f9ed04"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f00c328b8dae1828844bac019dfe425d10a2043cc70e2f967224c5392d19ad"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7965108069f9731306a882872c23ad4f5a8531668e82b27932a19814c52a8dd8"}, - {file = "bitarray-2.8.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:420aa610fe392c4ee700e474673276bb4f3c4f091d001f58b1f018bf650840c1"}, - {file = "bitarray-2.8.2-cp36-cp36m-win32.whl", hash = "sha256:b85929db81105c06e8292c05cac093068e86464555c628c03f99c9f8090d68d4"}, - {file = "bitarray-2.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:cba09dfd3aea2addc994eb21a861c3cea2d68141bb7ebe68b0e94c73405540f9"}, - {file = "bitarray-2.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:172169099797f1ec469b0aadb00c653193a74757f99312c9c17dc1a18d23d972"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a4fed240728dcc96966e0c4cfd3dce870525377a1cb5afac8e5cfe116ff7b"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff31bef13fd278446b6d1969a46db9f02c36fd905f3e75878f0fe17271f7d897"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb8b727cd9ddff848c5f73e65470abb110f026beab403bcebbd74e7439b9bd8f"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1356c86eefbde3fe8a3c39fb81bbc8b16acc8e442e191408042e8b1d6904e3"}, - {file = "bitarray-2.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7706336bd15acf4e42300579e42bef742c01a4eb202998f6c20c443a2ce5fd60"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a4b43949477dc2b0d3e1d8b7c413ed74f515cef01954cdcc3fb1e2dcc49f2aff"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:06d9de5db244c6e45a5318713367765de0a57d82ad616869a004a710a95541e9"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5569c8314335e92570c471d60b4b03eb2a4467864805a560d133d24b27b3961a"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:76a4faef4c31953aa7b9ebe00d162f7ce9bc03fc8d423ab2dc690a11d7520a8e"}, - {file = "bitarray-2.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1474db8c4297026e1daa1699e70e25e56dff91104fe025b1a9804332f2737604"}, - {file = "bitarray-2.8.2-cp37-cp37m-win32.whl", hash = "sha256:85b504f233f0484e9a74df4f286a9ae56fbbe2a648c45726761cf7b6f072cdc8"}, - {file = "bitarray-2.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3dde123ce85d1ba99d9bdf44b1b3174fa22bc8fb10004e0d72bb661a0444c1a9"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:23fae6a5a1403d16592b8823d5dea93f738c6e217a1e1bb0eefad242fb03d47f"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c44b3022115eb1697315bc51aeadbade1a19d7188bcda66c52d91209cf2963ca"}, - {file = "bitarray-2.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fea9354b7169810e2bdd6f3265ff128b564a25d38479b9ad0a9c5776e4fd0cfc"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f699bf2cb223aeec04a106003bd2bf8a4fc6d4c5eddf79cacecb6b267657ac5"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:462c9425fbc5315cbc20a72ca62558e5545bb0f6dc9355e2fa96fa747e9b1a80"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c8716b4c45fb128cd4da143749e276f150ecb0acb711f4969d7e7ebc9b2a675"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79fde5b27e35aedd958f5fb58ebabce47d7eddae5a5e3774088c30c9610195ef"}, - {file = "bitarray-2.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abf2593b91e36f1cb1c40ac895993c7d2eb30d3f1cb0954a80e5f13697b6b69"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ab2e03dd140ab93b91f94a785d1cd6082d5ab53ab6ec958726efa0ad17f7b87a"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9e895cc3e5ffee269dd9866097e227a68022ef2b78d627a6ed737534d0c88c14"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0bbeb7120ec1a9b26ce423e74cad7b414cea9e35f8e05599e3b3dceb87f4d1b6"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51d45d56be14b69720d11a8c61e101d86a65dc8a3a9f356bbe4d98cf4f3c5617"}, - {file = "bitarray-2.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:726a598e34657772e5f131115741ea8709e9b55fa35d63c4717bc16b2a737d38"}, - {file = "bitarray-2.8.2-cp38-cp38-win32.whl", hash = "sha256:ab87c4c50d65932788d058adbbd28a209144523ffacbab81dd41582ffce26af9"}, - {file = "bitarray-2.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:316147fb62c810a7667277e5ae7bb75b2871c32d2c398aeb4503cbd4cf3315e7"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36bdde1aba78e4a3a6ce5cbebd0a6bc967b0c3fbd8bd99a197dcc17d654f423c"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:932f7b77750dff7140522dc97dfd94533a599ef1c5d0be3733f556fd44a68821"}, - {file = "bitarray-2.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5819b95d0ccce864066f062d2329363ae8a64b9c3d076d039c75ffc9204c2a12"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c28b52e59a5e6aa00a929b35b04473bd479a74237ab1170c573c49e8aca61fe"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ecdd528268478efeb78ed0132b01104bda6cd8f10c8a57708fc87b1add77e4d"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f6f245d4a5e707d48274f38551b654a36db4fb83437c98be00d2019263aa364"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b088f06d9e2f523683ae363e227173ac454dbb56c938c6d42791fdd78bad8da7"}, - {file = "bitarray-2.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e883919cea8e446c5c49717a7ce5c93a016a02b9429b81d64b9ab1d80fc12e42"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:09d729420b8edc4d8a23a518ae4553074a0054d0441c1a461b425c2f033fab5e"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d0d0923087fe1f2d85daa68463d221e90b4b8ed0356480c887eea90b2a2cc7ee"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:70cebcf9bc345ac1e034fa781eac3619323eaf87f7bbe26f0e28850beb6f5634"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:890355bf6ba3dc04b5a23d1328eb1f6062165e6262197cebc9acfebdcb23144c"}, - {file = "bitarray-2.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f0b54b95e39036c116ffc057b3f56f6084ce88822de3d5d1f57fa38554ccf5c1"}, - {file = "bitarray-2.8.2-cp39-cp39-win32.whl", hash = "sha256:b499d93fa31a73e31ee62f2cbe07e4df833fd7151734b8f07c48ffe3e4547ec5"}, - {file = "bitarray-2.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:b007aaf5810c708c5a2778e371aa546d7084e4e9f82f65865b2ce5a182376f42"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1b734b074a09b1b2e1de7df423565412d9213faefa8ca422f32be756b189f729"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd074b06be9484040acb4c2c0462c4d19a43e377716be7ba10440f51a57bb98c"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678696bb613f0344b79be385747aae705b327a9a32ace45a353dd16497bc719"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb337ffa10824fa2025c4b1c06a2d809dbed4a4bf9e3ffb262676d084c4e0c50"}, - {file = "bitarray-2.8.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2b3c7aa2c9a6533dc7234d2a303efdcb9df3f4ac4d0919ec1caf568868f12a0a"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6765c47b487341837b3731cca3c8033b971ee082f6ab41cb430aa3447686eec"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8566b535bc4ebb26247d6f636a27bb0038bc93fa7e55121628f5cd6b0906ac"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56764825f64ab983d32b8c1d4ee483f415f2559e59388ba266a9fcafc44305bf"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f45f7d58c399e90ee3bddff4f3e2f53ff95c948b2d43de304266153ebd1d778"}, - {file = "bitarray-2.8.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:095851409e0db75b1416c8c3e24957135d5a2a206790578e43739e92a00c17c4"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8bb60d5a948f00901da1d7e4953189259b3c7ef79391fecd6f18db3f48a036fe"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2dc483ada55ef35990b67dc0e7a779f0b2ce79d156e452dc8b835b03c0dca9"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a35e308c23f039064600108fc1c8416bd102bc3cf3a6915761a9f7c801237e0"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa49f6cfcae4305d8cff028dc9c9a881189a38f7ca43c085aef894c58cb6fbde"}, - {file = "bitarray-2.8.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:111bf9913ebee4630e2cb43b61d0abb39813b231262b114e5268cd6a405a22b9"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b71d82e3f001bcb53463023f7f37e223fff56cf048f577c6d85597db94770f10"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:440c537fdf2eaee7fdd41fb1dce5701c490c1964fdb74225b10b49a7c45bc7b4"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c384c49ce52b82d5b0355000b8aeb7e3a7654997916c1e6fd9d29697edda1076"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27428d7b0e706307d0c697f81599e7af4f52e5873ea6bc269eae3604b16b81fe"}, - {file = "bitarray-2.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4963982d5da0825768f9a80760a8560c3e4cf711a9a7ea06ff9bcb7bd250b131"}, - {file = "bitarray-2.8.2.tar.gz", hash = "sha256:f90b2f44b5b23364d5fbade2c34652e15b1fcfe813c46f828e008f68a709160f"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, ] [[package]] name = "black" -version = "23.9.1" +version = "23.12.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, - {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, - {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, - {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, - {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, - {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, - {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, - {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, - {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, - {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, - {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -401,40 +439,38 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.28.62" +version = "1.34.30" description = "The AWS SDK for Python" -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "boto3-1.28.62-py3-none-any.whl", hash = "sha256:0dfa2fc96ccafce4feb23044d6cba8b25075ad428a0c450d369d099c6a1059d2"}, - {file = "boto3-1.28.62.tar.gz", hash = "sha256:148eeba0f1867b3db5b3e5ae2997d75a94d03fad46171374a0819168c36f7ed0"}, + {file = "boto3-1.34.30-py3-none-any.whl", hash = "sha256:cd6173380768faaecf6236dbdcec15d8d032cbb162ce354fdb111056a74fc298"}, + {file = "boto3-1.34.30.tar.gz", hash = "sha256:9e1476ce2b26437881a0381bf2daa54de619ac74ab4bd74278668acda6004a64"}, ] [package.dependencies] -botocore = ">=1.31.62,<1.32.0" +botocore = ">=1.34.30,<1.35.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.7.0,<0.8.0" +s3transfer = ">=0.10.0,<0.11.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.31.62" +version = "1.34.30" description = "Low-level, data-driven core of boto 3." -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "botocore-1.31.62-py3-none-any.whl", hash = "sha256:be792d806afc064694a2d0b9b25779f3ca0c1584b29a35ac32e67f0064ddb8b7"}, - {file = "botocore-1.31.62.tar.gz", hash = "sha256:272b78ac65256b6294cb9cdb0ac484d447ad3a85642e33cb6a3b1b8afee15a4c"}, + {file = "botocore-1.34.30-py3-none-any.whl", hash = "sha256:caf82d91c2ff61235284a07ffdfba006873e0752e00896052f901a37720cefa4"}, + {file = "botocore-1.34.30.tar.gz", hash = "sha256:e071a9766e7fc2221ca42ec01dfc54368a7518610787342ea622f6edc57f7891"}, ] [package.dependencies] @@ -443,25 +479,23 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.16.26)"] +crt = ["awscrt (==0.19.19)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -526,7 +560,6 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -536,109 +569,107 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -653,7 +684,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -663,186 +693,125 @@ files = [ [[package]] name = "contourpy" -version = "1.1.0" -description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "contourpy-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc"}, - {file = "contourpy-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, - {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, - {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, - {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, - {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, - {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, - {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, - {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, - {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, - {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, - {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, - {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, - {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, - {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, - {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, - {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, - {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, - {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, - {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, - {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, - {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, - {file = "contourpy-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76"}, - {file = "contourpy-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027"}, - {file = "contourpy-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104"}, - {file = "contourpy-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f"}, - {file = "contourpy-1.1.0.tar.gz", hash = "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21"}, -] - -[package.dependencies] -numpy = ">=1.16" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] - -[[package]] -name = "contourpy" -version = "1.1.1" +version = "1.2.0" description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, - {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, - {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, - {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, - {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, - {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, - {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, - {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, - {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, - {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, - {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, - {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, - {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, - {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, - {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, - {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, - {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, - {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, ] [package.dependencies] -numpy = {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""} +numpy = ">=1.20,<2.0" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "cryptography" -version = "41.0.4" +version = "42.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, - {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, - {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, - {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, - {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, - {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, - {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, - {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, - {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, - {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, + {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, + {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, + {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, + {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, + {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, + {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "cycler" version = "0.12.1" description = "Composable style cycles" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -856,105 +825,115 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cytoolz" -version = "0.12.2" +version = "0.12.3" description = "Cython implementation of Toolz: High performance functional utilities" -category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cytoolz-0.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bff49986c9bae127928a2f9fd6313146a342bfae8292f63e562f872bd01b871"}, - {file = "cytoolz-0.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:908c13f305d34322e11b796de358edaeea47dd2d115c33ca22909c5e8fb036fd"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:735147aa41b8eeb104da186864b55e2a6623c758000081d19c93d759cd9523e3"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d352d4de060604e605abdc5c8a5d0429d5f156cb9866609065d3003454d4cea"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89247ac220031a4f9f689688bcee42b38fd770d4cce294e5d914afc53b630abe"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9070ae35c410d644e6df98a8f69f3ed2807e657d0df2a26b2643127cbf6944a5"}, - {file = "cytoolz-0.12.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:843500cd3e4884b92fd4037912bc42d5f047108d2c986d36352e880196d465b0"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6a93644d7996fd696ab7f1f466cd75d718d0a00d5c8118b9fe8c64231dc1f85e"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96796594c770bc6587376e74ddc7d9c982d68f47116bb69d90873db5e0ea88b6"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:48425107fbb1af3f0f2410c004f16be10ffc9374358e5600b57fa543f46f8def"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:cde6dbb788a4cbc4a80a72aa96386ba4c2b17bdfff3ace0709799adbe16d6476"}, - {file = "cytoolz-0.12.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68ae7091cc73a752f0b938f15bb193de80ca5edf5ae2ea6360d93d3e9228357b"}, - {file = "cytoolz-0.12.2-cp310-cp310-win32.whl", hash = "sha256:997b7e0960072f6bb445402da162f964ea67387b9f18bda2361edcc026e13597"}, - {file = "cytoolz-0.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:663911786dcde3e4a5d88215c722c531c7548903dc07d418418c0d1c768072c0"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a7d8b869ded171f6cdf584fc2fc6ae03b30a0e1e37a9daf213a59857a62ed90"}, - {file = "cytoolz-0.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b28787eaf2174e68f0acb3c66f9c6b98bdfeb0930c0d0b08e1941c7aedc8d27"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00547da587f124b32b072ce52dd5e4b37cf199fedcea902e33c67548523e4678"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:275d53fd769df2102d6c9fc98e553bd8a9a38926f54d6b20cf29f0dd00bf3b75"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5556acde785a61d4cf8b8534ae109b023cbd2f9df65ee2afbe070be47c410f8c"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b41a85b9b9a2530b72b0d3d10e383fc3c2647ae88169d557d5e216f881860318"}, - {file = "cytoolz-0.12.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673d6e9e3aa86949343b46ac2b7be266c36e07ce77fa1d40f349e6987a814d6e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81e6a9a8fda78a2f4901d2915b25bf620f372997ca1f20a14f7cefef5ad6f6f4"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa44215bc31675a6380cd896dadb7f2054a7b94cfb87e53e52af844c65406a54"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a08b4346350660799d81d4016e748bcb134a9083301d41f9618f64a6077f89f2"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2fb740482794a72e2e5fec58e4d9b00dcd5a60a8cef68431ff12f2ba0e0d9a7e"}, - {file = "cytoolz-0.12.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9007bb1290c79402be6b84bcf9e7a622a073859d61fcee146dc7bc47afe328f3"}, - {file = "cytoolz-0.12.2-cp311-cp311-win32.whl", hash = "sha256:a973f5286758f76824ecf19ae1999f6697371a9121c8f163295d181d19a819d7"}, - {file = "cytoolz-0.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:1ce324d1b413636ea5ee929f79637821f13c9e55e9588f38228947294944d2ed"}, - {file = "cytoolz-0.12.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c08094b9e5d1b6dfb0845a0253cc2655ca64ce70d15162dfdb102e28c8993493"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baf020f4b708f800b353259cd7575e335a79f1ac912d9dda55b2aa0bf3616e42"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4416ee86a87180b6a28e7483102c92debc077bec59c67eda8cc63fc52a218ac0"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ee222671eed5c5b16a5ad2aea07f0a715b8b199ee534834bc1dd2798f1ade7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad92e37be0b106fdbc575a3a669b43b364a5ef334495c9764de4c2d7541f7a99"}, - {file = "cytoolz-0.12.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460c05238fbfe6d848141669d17a751a46c923f9f0c9fd8a3a462ab737623a44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9e5075e30be626ef0f9bedf7a15f55ed4d7209e832bc314fdc232dbd61dcbf44"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:03b58f843f09e73414e82e57f7e8d88f087eaabf8f276b866a40661161da6c51"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e4e612b7ecc9596e7c859cd9e0cd085e6d0c576b4f0d917299595eb56bf9c05"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:08a0e03f287e45eb694998bb55ac1643372199c659affa8319dfbbdec7f7fb3c"}, - {file = "cytoolz-0.12.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b029bdd5a8b6c9a7c0e8fdbe4fc25ffaa2e09b77f6f3462314696e3a20511829"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win32.whl", hash = "sha256:18580d060fa637ff01541640ecde6de832a248df02b8fb57e6dd578f189d62c7"}, - {file = "cytoolz-0.12.2-cp36-cp36m-win_amd64.whl", hash = "sha256:97cf514a9f3426228d8daf880f56488330e4b2948a6d183a106921217850d9eb"}, - {file = "cytoolz-0.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18a0f838677f9510aef0330c0096778dd6406d21d4ff9504bf79d85235a18460"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb081b2b02bf4405c804de1ece6f904916838ab0e057f1446e4ac12fac827960"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57233e1600560ceb719bed759dc78393edd541b9a3e7fefc3079abd83c26a6ea"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0295289c4510efa41174850e75bc9188f82b72b1b54d0ea57d1781729c2924d5"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a92aab8dd1d427ac9bc7480cfd3481dbab0ef024558f2f5a47de672d8a5ffaa"}, - {file = "cytoolz-0.12.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d3495235af09f21aa92a7cdd51504bda640b108b6be834448b774f52852c09"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9c690b359f503f18bf1c46a6456370e4f6f3fc4320b8774ae69c4f85ecc6c94"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:481e3129a76ea01adcc0e7097ccb8dbddab1cfc40b6f0e32c670153512957c0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55e94124af9c8fbb1df54195cc092688fdad0765641b738970b6f1d5ea72e776"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5616d386dfbfba7c39e9418ba668c734f6ceaacc0130877e8a100cad11e6838b"}, - {file = "cytoolz-0.12.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:732d08228fa8d366fec284f7032cc868d28a99fa81fc71e3adf7ecedbcf33a0f"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win32.whl", hash = "sha256:f039c5373f7b314b151432c73219216857b19ab9cb834f0eb5d880f74fc7851c"}, - {file = "cytoolz-0.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:246368e983eaee9851b15d7755f82030eab4aa82098d2a34f6bef9c689d33fcc"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81074edf3c74bc9bd250d223408a5df0ff745d1f7a462597536cd26b9390e2d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:960d85ebaa974ecea4e71fa56d098378fa51fd670ee744614cbb95bf95e28fc7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c8d0dff4865da54ae825d43e1721925721b19f3b9aca8e730c2ce73dee2c630"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9d12436fd64937bd2c9609605f527af7f1a8db6e6637639b44121c0fe715d6"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd461e402e24929d866f05061d2f8337e3a8456e75e21b72c125abff2477c7f7"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0568d4da0a9ee9f9f5ab318f6501557f1cfe26d18c96c8e0dac7332ae04c6717"}, - {file = "cytoolz-0.12.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:101b5bd32badfc8b1f9c7be04ba3ae04fb47f9c8736590666ce9449bff76e0b1"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bb624dbaef4661f5e3625c1e39ad98ecceef281d1380e2774d8084ad0810275"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3e993804e6b04113d61fdb9541b6df2f096ec265a506dad7437517470919c90f"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ab911033e5937fc221a2c165acce7f66ae5ac9d3e54bec56f3c9c197a96be574"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6de6a4bdfaee382c2de2a3580b3ae76fce6105da202bbd835e5efbeae6a9c6e"}, - {file = "cytoolz-0.12.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9480b4b327be83c4d29cb88bcace761b11f5e30198ffe2287889455c6819e934"}, - {file = "cytoolz-0.12.2-cp38-cp38-win32.whl", hash = "sha256:4180b2785d1278e6abb36a72ac97c92432db53fa2df00ee943d2c15a33627d31"}, - {file = "cytoolz-0.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:d0086ba8d41d73647b13087a3ca9c020f6bfec338335037e8f5172b4c7c8dce5"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d29988bde28a90a00367edcf92afa1a2f7ecf43ea3ae383291b7da6d380ccc25"}, - {file = "cytoolz-0.12.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24c0d71e9ac91f4466b1bd280f7de43aa4d94682daaf34d85d867a9b479b87cc"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa436abd4ac9ca71859baf5794614e6ec8fa27362f0162baedcc059048da55f7"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45c7b4eac7571707269ebc2893facdf87e359cd5c7cfbfa9e6bd8b33fb1079c5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:294d24edc747ef4e1b28e54365f713becb844e7898113fafbe3e9165dc44aeea"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:478051e5ef8278b2429864c8d148efcebdc2be948a61c9a44757cd8c816c98f5"}, - {file = "cytoolz-0.12.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14108cafb140dd68fdda610c2bbc6a37bf052cd48cfebf487ed44145f7a2b67f"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fef7b602ccf8a3c77ab483479ccd7a952a8c5bb1c263156671ba7aaa24d1035"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9bf51354e15520715f068853e6ab8190e77139940e8b8b633bdb587956a08fb0"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:388f840fd911d61a96e9e595eaf003f9dc39e847c9060b8e623ab29e556f009b"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a67f75cc51a2dc7229a8ac84291e4d61dc5abfc8940befcf37a2836d95873340"}, - {file = "cytoolz-0.12.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63b31345e20afda2ae30dba246955517a4264464d75e071fc2fa641e88c763ec"}, - {file = "cytoolz-0.12.2-cp39-cp39-win32.whl", hash = "sha256:f6e86ac2b45a95f75c6f744147483e0fc9697ce7dfe1726083324c236f873f8b"}, - {file = "cytoolz-0.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:5998f81bf6a2b28a802521efe14d9fc119f74b64e87b62ad1b0e7c3d8366d0c7"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:593e89e2518eaf81e96edcc9ef2c5fca666e8fc922b03d5cb7a7b8964dbee336"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff451d614ca1d4227db0ffa627fb51df71968cf0d9baf0210528dad10fdbc3ab"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad9ea4a50d2948738351790047d45f2b1a023facc01bf0361988109b177e8b2f"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbe038bb78d599b5a29d09c438905defaa615a522bc7e12f8016823179439497"}, - {file = "cytoolz-0.12.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d494befe648c13c98c0f3d56d05489c839c9228a32f58e9777305deb6c2c1cee"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c26805b6c8dc8565ed91045c44040bf6c0fe5cb5b390c78cd1d9400d08a6cd39"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4e32badb2ccf1773e1e74020b7e3b8caf9e92f842c6be7d14888ecdefc2c6c"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce7889dc3701826d519ede93cdff11940fb5567dbdc165dce0e78047eece02b7"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c820608e7077416f766b148d75e158e454881961881b657cff808529d261dd24"}, - {file = "cytoolz-0.12.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:698da4fa1f7baeea0607738cb1f9877ed1ba50342b29891b0223221679d6f729"}, - {file = "cytoolz-0.12.2.tar.gz", hash = "sha256:31d4b0455d72d914645f803d917daf4f314d115c70de0578d3820deb8b101f66"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, ] [package.dependencies] @@ -967,7 +946,6 @@ cython = ["cython"] name = "datumaro" version = "0.3" description = "Dataset Management Framework (Datumaro)" -category = "main" optional = false python-versions = ">=3.7" files = [] @@ -1010,7 +988,6 @@ resolved_reference = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11" name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1022,7 +999,6 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1032,26 +1008,24 @@ files = [ [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] name = "eth-abi" -version = "4.2.1" +version = "5.0.0" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" -category = "main" optional = false -python-versions = ">=3.7.2, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth_abi-4.2.1-py3-none-any.whl", hash = "sha256:abd83410a5326145bf178675c276de0ed154f6dc695dcad1beafaa44d97f44ae"}, - {file = "eth_abi-4.2.1.tar.gz", hash = "sha256:60d88788d53725794cdb07c0f0bb0df2a31a6e1ad19644313fe6117ac24eeeb0"}, + {file = "eth_abi-5.0.0-py3-none-any.whl", hash = "sha256:936a715d7366ac13cac665cbdaf01cc4aabbe8c2d810d716287a9634f9665e01"}, + {file = "eth_abi-5.0.0.tar.gz", hash = "sha256:89c4454d794d9ed92ad5cb2794698c5cee6b7b3ca6009187d0e282adc7f9b6dc"}, ] [package.dependencies] @@ -1060,22 +1034,20 @@ eth-utils = ">=2.0.0" parsimonious = ">=0.9.0,<0.10.0" [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-xdist (>=2.4.0)"] tools = ["hypothesis (>=4.18.2,<5.0.0)"] [[package]] name = "eth-account" -version = "0.9.0" +version = "0.10.0" description = "eth-account: Sign Ethereum transactions and messages with local private keys" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ - {file = "eth-account-0.9.0.tar.gz", hash = "sha256:5f66ecb7bc52569924dfaf4a9add501b1c2a4901eec74e3c0598cd26d0971777"}, - {file = "eth_account-0.9.0-py3-none-any.whl", hash = "sha256:35636ca14e9063dea233648703338be1a44e8cb1a2f9de1519d2b1be4655da59"}, + {file = "eth-account-0.10.0.tar.gz", hash = "sha256:474a2fccf7286230cf66502565f03b536921d7e1fdfceba198e42160e5ac4bc1"}, + {file = "eth_account-0.10.0-py3-none-any.whl", hash = "sha256:b7a83f506a8edf57926569e5f04471ce3f1700e572d3421b4ad0dad7a26c0978"}, ] [package.dependencies] @@ -1085,7 +1057,7 @@ eth-keyfile = ">=0.6.0" eth-keys = ">=0.4.0" eth-rlp = ">=0.3.0" eth-utils = ">=2.0.0" -hexbytes = ">=0.1.0" +hexbytes = ">=0.1.0,<0.4.0" rlp = ">=1.0.0" [package.extras] @@ -1096,127 +1068,114 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdis [[package]] name = "eth-hash" -version = "0.5.2" +version = "0.6.0" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-hash-0.5.2.tar.gz", hash = "sha256:1b5f10eca7765cc385e1430eefc5ced6e2e463bb18d1365510e2e539c1a6fe4e"}, - {file = "eth_hash-0.5.2-py3-none-any.whl", hash = "sha256:251f62f6579a1e247561679d78df37548bd5f59908da0b159982bf8293ad32f0"}, + {file = "eth-hash-0.6.0.tar.gz", hash = "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478"}, + {file = "eth_hash-0.6.0-py3-none-any.whl", hash = "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3"}, ] [package.dependencies] pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -doc = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] pycryptodome = ["pycryptodome (>=3.6.6,<4)"] pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-keyfile" -version = "0.6.1" -description = "A library for handling the encrypted keyfiles used to store ethereum private keys." -category = "main" +version = "0.7.0" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" optional = false -python-versions = "*" +python-versions = ">=3.8, <4" files = [ - {file = "eth-keyfile-0.6.1.tar.gz", hash = "sha256:471be6e5386fce7b22556b3d4bde5558dbce46d2674f00848027cb0a20abdc8c"}, - {file = "eth_keyfile-0.6.1-py3-none-any.whl", hash = "sha256:609773a1ad5956944a33348413cad366ec6986c53357a806528c8f61c4961560"}, + {file = "eth-keyfile-0.7.0.tar.gz", hash = "sha256:6bdb8110c3a50439deb68a04c93c9d5ddd5402353bfae1bf4cfca1d6dff14fcf"}, + {file = "eth_keyfile-0.7.0-py3-none-any.whl", hash = "sha256:6a89b231a2fe250c3a8f924f2695bb9cce33ddd0d6f7ebbcdacd183d7f83d537"}, ] [package.dependencies] -eth-keys = ">=0.4.0,<0.5.0" -eth-utils = ">=2,<3" +eth-keys = ">=0.4.0" +eth-utils = ">=2" pycryptodome = ">=3.6.6,<4" [package.extras] -dev = ["bumpversion (>=0.5.3,<1)", "eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "flake8 (==4.0.1)", "idna (==2.7)", "pluggy (>=1.0.0,<2)", "pycryptodome (>=3.6.6,<4)", "pytest (>=6.2.5,<7)", "requests (>=2.20,<3)", "setuptools (>=38.6.0)", "tox (>=2.7.0)", "twine", "wheel"] -keyfile = ["eth-keys (>=0.4.0,<0.5.0)", "eth-utils (>=2,<3)", "pycryptodome (>=3.6.6,<4)"] -lint = ["flake8 (==4.0.1)"] -test = ["pytest (>=6.2.5,<7)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-keys" -version = "0.4.0" -description = "Common API for Ethereum key operations." -category = "main" +version = "0.5.0" +description = "eth-keys: Common API for Ethereum key operations" optional = false -python-versions = "*" +python-versions = ">=3.8, <4" files = [ - {file = "eth-keys-0.4.0.tar.gz", hash = "sha256:7d18887483bc9b8a3fdd8e32ddcb30044b9f08fcb24a380d93b6eee3a5bb3216"}, - {file = "eth_keys-0.4.0-py3-none-any.whl", hash = "sha256:e07915ffb91277803a28a379418bdd1fad1f390c38ad9353a0f189789a440d5d"}, + {file = "eth-keys-0.5.0.tar.gz", hash = "sha256:a0abccb83f3d84322591a2c047a1e3aa52ea86b185fa3e82ce311d120ca2791e"}, + {file = "eth_keys-0.5.0-py3-none-any.whl", hash = "sha256:b2bed3ff3bcede68cc0cd4458c7147baaeaac1211a1efdb6ca019f9d3d989f2b"}, ] [package.dependencies] -eth-typing = ">=3.0.0,<4" -eth-utils = ">=2.0.0,<3.0.0" +eth-typing = ">=3" +eth-utils = ">=2" [package.extras] -coincurve = ["coincurve (>=7.0.0,<16.0.0)"] -dev = ["asn1tools (>=0.146.2,<0.147)", "bumpversion (==0.5.3)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)", "factory-boy (>=3.0.1,<3.1)", "flake8 (==3.0.4)", "hypothesis (>=5.10.3,<6.0.0)", "mypy (==0.782)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)", "tox (==3.20.0)", "twine"] -eth-keys = ["eth-typing (>=3.0.0,<4)", "eth-utils (>=2.0.0,<3.0.0)"] -lint = ["flake8 (==3.0.4)", "mypy (==0.782)"] -test = ["asn1tools (>=0.146.2,<0.147)", "eth-hash[pycryptodome]", "eth-hash[pysha3]", "factory-boy (>=3.0.1,<3.1)", "hypothesis (>=5.10.3,<6.0.0)", "pyasn1 (>=0.4.5,<0.5)", "pytest (==6.2.5)"] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3,<6)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3,<6)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] [[package]] name = "eth-rlp" -version = "0.3.0" +version = "1.0.1" description = "eth-rlp: RLP definitions for common Ethereum objects in Python" -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-rlp-0.3.0.tar.gz", hash = "sha256:f3263b548df718855d9a8dbd754473f383c0efc82914b0b849572ce3e06e71a6"}, - {file = "eth_rlp-0.3.0-py3-none-any.whl", hash = "sha256:e88e949a533def85c69fa94224618bbbd6de00061f4cff645c44621dab11cf33"}, + {file = "eth-rlp-1.0.1.tar.gz", hash = "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027"}, + {file = "eth_rlp-1.0.1-py3-none-any.whl", hash = "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517"}, ] [package.dependencies] -eth-utils = ">=2.0.0,<3" +eth-utils = ">=2.0.0" hexbytes = ">=0.1.0,<1" -rlp = ">=0.6.0,<4" +rlp = ">=0.6.0" +typing-extensions = {version = ">=4.0.1", markers = "python_version <= \"3.11\""} [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "eth-hash[pycryptodome]", "flake8 (==3.7.9)", "ipython", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)", "tox (==3.14.6)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)", "towncrier (>=19.2.0,<20)"] -lint = ["flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.770)", "pydocstyle (>=3.0.0,<4)"] -test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (==3.14.6)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-typing" -version = "3.5.0" +version = "4.0.0" description = "eth-typing: Common type annotations for ethereum python packages" -category = "main" optional = false -python-versions = ">=3.7.2, <4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-typing-3.5.0.tar.gz", hash = "sha256:a92f6896896752143a4704c57441eedf7b1f65d5df4b1c20cb802bb4aa602d7e"}, - {file = "eth_typing-3.5.0-py3-none-any.whl", hash = "sha256:a773dbb7d78fcd1539c30264193ca26ec965f3abca2711748e307f117b0a10f5"}, + {file = "eth-typing-4.0.0.tar.gz", hash = "sha256:9af0b6beafbc5c2e18daf19da5f5a68315023172c4e79d149e12ad10a3d3f731"}, + {file = "eth_typing-4.0.0-py3-none-any.whl", hash = "sha256:7e556bea322b6e8c0a231547b736c258e10ce9eed5ddc254f51031b12af66a16"}, ] -[package.dependencies] -typing-extensions = ">=4.0.1" - [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] -docs = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=6.0.0)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-utils" -version = "2.2.1" +version = "3.0.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -category = "main" optional = false -python-versions = ">=3.7,<4" +python-versions = ">=3.8, <4" files = [ - {file = "eth-utils-2.2.1.tar.gz", hash = "sha256:f79a95f86dd991344697c763db40271dbe43fbbcd5776f49b0c4fb7b645ee1c4"}, - {file = "eth_utils-2.2.1-py3-none-any.whl", hash = "sha256:60fc999c1b4ae011ab600b01a3eb5375156f3bc46e7cd1a83ca9e6e14bb9b13c"}, + {file = "eth-utils-3.0.0.tar.gz", hash = "sha256:8721869568448349bceae63c277b75758d11e0dc190e7ef31e161b89619458f1"}, + {file = "eth_utils-3.0.0-py3-none-any.whl", hash = "sha256:9a284106acf6f6ce91ddf792489cf8bd4c681fd5ae7653d2f3d5d100be5c3905"}, ] [package.dependencies] @@ -1226,21 +1185,19 @@ eth-typing = ">=3.0.0" toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} [package.extras] -dev = ["black (>=23)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "flake8 (==3.8.3)", "hypothesis (>=4.43.0)", "ipython", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "types-setuptools", "wheel"] -doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] -lint = ["black (>=23)", "flake8 (==3.8.3)", "isort (>=5.11.0)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "types-setuptools"] -test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "types-setuptools"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.5.1)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -1250,7 +1207,6 @@ test = ["pytest (>=6)"] name = "fastapi" version = "0.98.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1267,77 +1223,75 @@ all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "filelock" -version = "3.12.4" +version = "3.13.1" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, - {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] -typing = ["typing-extensions (>=4.7.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "fonttools" -version = "4.43.1" +version = "4.47.2" description = "Tools to manipulate font files" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, - {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, - {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, - {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, - {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, - {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, - {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, - {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, - {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, - {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, - {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, - {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, - {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"}, + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c"}, + {file = "fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0"}, + {file = "fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703"}, + {file = "fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c"}, + {file = "fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f"}, + {file = "fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085"}, + {file = "fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:254d9a6f7be00212bf0c3159e0a420eb19c63793b2c05e049eb337f3023c5ecc"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eabae77a07c41ae0b35184894202305c3ad211a93b2eb53837c2a1143c8bc952"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86a5ab2873ed2575d0fcdf1828143cfc6b977ac448e3dc616bb1e3d20efbafa"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13819db8445a0cec8c3ff5f243af6418ab19175072a9a92f6cc8ca7d1452754b"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4e743935139aa485fe3253fc33fe467eab6ea42583fa681223ea3f1a93dd01e6"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d49ce3ea7b7173faebc5664872243b40cf88814ca3eb135c4a3cdff66af71946"}, + {file = "fonttools-4.47.2-cp38-cp38-win32.whl", hash = "sha256:94208ea750e3f96e267f394d5588579bb64cc628e321dbb1d4243ffbc291b18b"}, + {file = "fonttools-4.47.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f750037e02beb8b3569fbff701a572e62a685d2a0e840d75816592280e5feae"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d71606c9321f6701642bd4746f99b6089e53d7e9817fc6b964e90d9c5f0ecc6"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e0427864c6c91cf77f16d1fb9bf1bbf7453e824589e8fb8461b6ee1144f506"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a00bd0e68e88987dcc047ea31c26d40a3c61185153b03457956a87e39d43c37"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d77479fb885ef38a16a253a2f4096bc3d14e63a56d6246bfdb56365a12b20c"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5465df494f20a7d01712b072ae3ee9ad2887004701b95cb2cc6dcb9c2c97a899"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c811d3c73b6abac275babb8aa439206288f56fdb2c6f8835e3d7b70de8937a7"}, + {file = "fonttools-4.47.2-cp39-cp39-win32.whl", hash = "sha256:5b60e3afa9635e3dfd3ace2757039593e3bd3cf128be0ddb7a1ff4ac45fa5a50"}, + {file = "fonttools-4.47.2-cp39-cp39-win_amd64.whl", hash = "sha256:7ee48bd9d6b7e8f66866c9090807e3a4a56cf43ffad48962725a190e0dd774c8"}, + {file = "fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184"}, + {file = "fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres", "pycairo", "scipy"] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] @@ -1345,161 +1299,170 @@ repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] +unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "frozenlist" -version = "1.4.0" +version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, - {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, - {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, - {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, - {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] name = "greenlet" -version = "3.0.0" +version = "3.0.3" description = "Lightweight in-process concurrent programming" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, - {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, - {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, - {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, - {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, - {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, - {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, - {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, - {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, - {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, - {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, - {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, - {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, - {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, - {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, - {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, - {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, - {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, - {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, - {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, - {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, - {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, - {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, - {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, - {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, - {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, - {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, - {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, - {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, - {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, - {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, -] - -[package.extras] -docs = ["Sphinx"] + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] test = ["objgraph", "psutil"] [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1511,7 +1474,6 @@ files = [ name = "h5py" version = "3.10.0" description = "Read and write HDF5 files from Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1549,7 +1511,6 @@ numpy = ">=1.17.3" name = "hexbytes" version = "0.3.1" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -1567,7 +1528,6 @@ test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>= name = "httpcore" version = "0.17.3" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1579,17 +1539,16 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httpx" version = "0.24.1" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1605,20 +1564,19 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "human-protocol-sdk" -version = "1.1.15" +version = "1.1.19" description = "A python library to launch escrow contracts to the HUMAN network." -category = "main" optional = false python-versions = "*" files = [ - {file = "human-protocol-sdk-1.1.15.tar.gz", hash = "sha256:b5bdd8072b5354e548a6631967a41a81f2c8d79acad08609ef798e21637e9c53"}, - {file = "human_protocol_sdk-1.1.15-py3-none-any.whl", hash = "sha256:93c82d98d9bc3cd574306dcfadcfabf117b6d8cdb7d4abafb29a7aa5c7ae5a15"}, + {file = "human-protocol-sdk-1.1.19.tar.gz", hash = "sha256:c00fb0bc58607c57a062d78f916af793d436da9c84a05030261e08c930b5268d"}, + {file = "human_protocol_sdk-1.1.19-py3-none-any.whl", hash = "sha256:725d3ef50d7d23b6a90cadf14e2cfd59bc698beed9bcda239b55141b1ad6e3e1"}, ] [package.dependencies] @@ -1627,30 +1585,29 @@ cryptography = "*" minio = "*" pgpy = "*" validators = "0.20.0" -web3 = ">=6.8.0,<6.9.0" +web3 = "==6.8.*" [package.extras] agreement = ["numpy", "pyerf"] [[package]] name = "hypothesis" -version = "6.87.3" +version = "6.97.3" description = "A library for property-based testing" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.87.3-py3-none-any.whl", hash = "sha256:684a7b56a4a2e990cb0efb3124c2d886c5138453550b6f4f4a3b75bfc8ef24d4"}, - {file = "hypothesis-6.87.3.tar.gz", hash = "sha256:e67391efb9e6f663031f493d04b5edfb2e47bfc5a6ea56190aed3bc7993d5899"}, + {file = "hypothesis-6.97.3-py3-none-any.whl", hash = "sha256:6256d768ec866426bfce6ed78418c6e3e43119a0dbece2e0229a1ae5929ae53d"}, + {file = "hypothesis-6.97.3.tar.gz", hash = "sha256:00216ddadaee17ba73451e262f973970a97d34fd75ec34ef57510147264c34d1"}, ] [package.dependencies] -attrs = ">=19.2.0" +attrs = ">=22.2.0" exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.3)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2023.4)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] dateutil = ["python-dateutil (>=1.4)"] @@ -1663,18 +1620,17 @@ pandas = ["pandas (>=1.1)"] pytest = ["pytest (>=4.6)"] pytz = ["pytz (>=2014.1)"] redis = ["redis (>=3.0.0)"] -zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.3)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2023.4)"] [[package]] name = "identify" -version = "2.5.30" +version = "2.5.33" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, - {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, ] [package.extras] @@ -1682,21 +1638,19 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1706,27 +1660,22 @@ files = [ [[package]] name = "isort" -version = "5.12.0" +version = "5.13.2" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jmespath" version = "1.0.1" description = "JSON Matching Expressions" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1736,14 +1685,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.19.1" +version = "4.21.1" description = "An implementation of JSON Schema validation for Python" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, - {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, ] [package.dependencies] @@ -1758,24 +1706,22 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.7.1" +version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.7.1-py3-none-any.whl", hash = "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1"}, - {file = "jsonschema_specifications-2023.7.1.tar.gz", hash = "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb"}, + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, ] [package.dependencies] -referencing = ">=0.28.0" +referencing = ">=0.31.0" [[package]] name = "kiwisolver" version = "1.4.5" description = "A fast implementation of the Cassowary constraint solver" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1887,94 +1833,92 @@ files = [ [[package]] name = "lru-dict" -version = "1.2.0" +version = "1.3.0" description = "An Dict like LRU container." -category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, - {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, - {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, - {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, - {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, - {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, - {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, - {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, - {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, - {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, - {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, - {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, - {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, - {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, - {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, - {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, - {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, - {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, - {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, - {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, - {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, - {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, - {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, - {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, - {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, - {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, - {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, - {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, - {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, - {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, - {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, - {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, - {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, - {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, + {file = "lru-dict-1.3.0.tar.gz", hash = "sha256:54fd1966d6bd1fcde781596cb86068214edeebff1db13a2cea11079e3fd07b6b"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4073333894db9840f066226d50e6f914a2240711c87d60885d8c940b69a6673f"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0ad6361e4dd63b47b2fc8eab344198f37387e1da3dcfacfee19bafac3ec9f1eb"}, + {file = "lru_dict-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c637ab54b8cd9802fe19b260261e38820d748adf7606e34045d3c799b6dde813"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fce5f95489ca1fc158cc9fe0f4866db9cec82c2be0470926a9080570392beaf"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2bf2e24cf5f19c3ff69bf639306e83dced273e6fa775b04e190d7f5cd16f794"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90059f7701bef3c4da073d6e0434a9c7dc551d5adce30e6b99ef86b186f4b4a"}, + {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ecb7ae557239c64077e9b26a142eb88e63cddb104111a5122de7bebbbd00098"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6af36166d22dba851e06a13e35bbf33845d3dd88872e6aebbc8e3e7db70f4682"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee38d420c77eed548df47b7d74b5169a98e71c9e975596e31ab808e76d11f09"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0e1845024c31e6ff246c9eb5e6f6f1a8bb564c06f8a7d6d031220044c081090b"}, + {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ca5474b1649555d014be1104e5558a92497509021a5ba5ea6e9b492303eb66b"}, + {file = "lru_dict-1.3.0-cp310-cp310-win32.whl", hash = "sha256:ebb03a9bd50c2ed86d4f72a54e0aae156d35a14075485b2127c4b01a3f4a63fa"}, + {file = "lru_dict-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:04cda617f4e4c27009005d0a8185ef02829b14b776d2791f5c994cc9d668bc24"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:20c595764695d20bdc3ab9b582e0cc99814da183544afb83783a36d6741a0dac"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9b30a8f50c3fa72a494eca6be5810a1b5c89e4f0fda89374f0d1c5ad8d37d51"}, + {file = "lru_dict-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9710737584650a4251b9a566cbb1a86f83437adb209c9ba43a4e756d12faf0d7"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b84c321ae34f2f40aae80e18b6fa08b31c90095792ab64bb99d2e385143effaa"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eed24272b4121b7c22f234daed99899817d81d671b3ed030c876ac88bc9dc890"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd13af06dab7c6ee92284fd02ed9a5613a07d5c1b41948dc8886e7207f86dfd"}, + {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1efc59bfba6aac33684d87b9e02813b0e2445b2f1c444dae2a0b396ad0ed60c"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfaf75ac574447afcf8ad998789071af11d2bcf6f947643231f692948839bd98"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c95f8751e2abd6f778da0399c8e0239321d560dbc58cb063827123137d213242"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:abd0c284b26b5c4ee806ca4f33ab5e16b4bf4d5ec9e093e75a6f6287acdde78e"}, + {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a47740652b25900ac5ce52667b2eade28d8b5fdca0ccd3323459df710e8210a"}, + {file = "lru_dict-1.3.0-cp311-cp311-win32.whl", hash = "sha256:a690c23fc353681ed8042d9fe8f48f0fb79a57b9a45daea2f0be1eef8a1a4aa4"}, + {file = "lru_dict-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:efd3f4e0385d18f20f7ea6b08af2574c1bfaa5cb590102ef1bee781bdfba84bc"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c279068f68af3b46a5d649855e1fb87f5705fe1f744a529d82b2885c0e1fc69d"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:350e2233cfee9f326a0d7a08e309372d87186565e43a691b120006285a0ac549"}, + {file = "lru_dict-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4eafb188a84483b3231259bf19030859f070321b00326dcb8e8c6cbf7db4b12f"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73593791047e36b37fdc0b67b76aeed439fcea80959c7d46201240f9ec3b2563"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1958cb70b9542773d6241974646e5410e41ef32e5c9e437d44040d59bd80daf2"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc1cd3ed2cee78a47f11f3b70be053903bda197a873fd146e25c60c8e5a32cd6"}, + {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82eb230d48eaebd6977a92ddaa6d788f14cf4f4bcf5bbffa4ddfd60d051aa9d4"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5ad659cbc349d0c9ba8e536b5f40f96a70c360f43323c29f4257f340d891531c"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba490b8972531d153ac0d4e421f60d793d71a2f4adbe2f7740b3c55dce0a12f1"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:c0131351b8a7226c69f1eba5814cbc9d1d8daaf0fdec1ae3f30508e3de5262d4"}, + {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e88dba16695f17f41701269fa046197a3fd7b34a8dba744c8749303ddaa18df"}, + {file = "lru_dict-1.3.0-cp312-cp312-win32.whl", hash = "sha256:6ffaf595e625b388babc8e7d79b40f26c7485f61f16efe76764e32dce9ea17fc"}, + {file = "lru_dict-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c265f16c936a8ff3bb4b8a4bda0be94c15ec28b63e99fdb1439c1ffe4cd437db"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:784ca9d3b0730b3ec199c0a58f66264c63dd5d438119c739c349a6a9be8e5f6e"}, + {file = "lru_dict-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e13b2f58f647178470adaa14603bb64cc02eeed32601772ccea30e198252883c"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ffbce5c2e80f57937679553c8f27e61ec327c962bf7ea0b15f1d74277fd5363"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7969cb034b3ccc707aff877c73c225c32d7e2a7981baa8f92f5dd4d468fe8c33"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca9ab676609cce85dd65d91c275e47da676d13d77faa72de286fbea30fbaa596"}, + {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27c078b5d75989952acbf9b77e14c3dadc468a4aafe85174d548afbc5efc38b"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6123aefe97762ad74215d05320a7f389f196f0594c8813534284d4eafeca1a96"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd869cadba9a63e1e7fe2dced4a5747d735135b86016b0a63e8c9e324ab629ac"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40a8daddc29c7edb09dfe44292cf111f1e93a8344349778721d430d336b50505"}, + {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a03170e4152836987a88dcebde61aaeb73ab7099a00bb86509d45b3fe424230"}, + {file = "lru_dict-1.3.0-cp38-cp38-win32.whl", hash = "sha256:3b4f121afe10f5a82b8e317626eb1e1c325b3f104af56c9756064cd833b1950b"}, + {file = "lru_dict-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:1470f5828c7410e16c24b5150eb649647986e78924816e6fb0264049dea14a2b"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c9f746a9917e784fffcedeac4c8c47a3dbd90cbe13b69e9140182ad97ce4b7"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2789296819525a1f3204072dfcf3df6db8bcf69a8fc740ffd3de43a684ea7002"}, + {file = "lru_dict-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:170b66d29945391460351588a7bd8210a95407ae82efe0b855e945398a1d24ea"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774ca88501a9effe8797c3db5a6685cf20978c9cb0fe836b6813cfe1ca60d8c9"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df2e119c6ae412d2fd641a55f8a1e2e51f45a3de3449c18b1b86c319ab79e0c4"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28aa1ea42a7e48174bf513dc2416fea7511a547961e678dc6f5670ca987c18cb"}, + {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9537e1cee6fa582cb68f2fb9ce82d51faf2ccc0a638b275d033fdcb1478eb80b"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:64545fca797fe2c68c5168efb5f976c6e1459e058cab02445207a079180a3557"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a193a14c66cfc0c259d05dddc5e566a4b09e8f1765e941503d065008feebea9d"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb1de0ce4137b060abaafed8474cc0ebd12cedd88aaa7f7b3ebb1ddfba86ae0"}, + {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8551ccab1349d4bebedab333dfc8693c74ff728f4b565fe15a6bf7d296bd7ea9"}, + {file = "lru_dict-1.3.0-cp39-cp39-win32.whl", hash = "sha256:6cb0be5e79c3f34d69b90d8559f0221e374b974b809a22377122c4b1a610ff67"}, + {file = "lru_dict-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9f725f2a0bdf1c18735372d5807af4ea3b77888208590394d4660e3d07971f21"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f8f7824db5a64581180ab9d09842e6dd9fcdc46aac9cb592a0807cd37ea55680"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acd04b7e7b0c0c192d738df9c317093335e7282c64c9d1bb6b7ebb54674b4e24"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5c20f236f27551e3f0adbf1a987673fb1e9c38d6d284502cd38f5a3845ef681"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca3703ff03b03a1848c563bc2663d0ad813c1cd42c4d9cf75b623716d4415d9a"}, + {file = "lru_dict-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a9fb71ba262c6058a0017ce83d343370d0a0dbe2ae62c2eef38241ec13219330"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f5b88a7c39e307739a3701194993455968fcffe437d1facab93546b1b8a334c1"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2682bfca24656fb7a643621520d57b7fe684ed5fa7be008704c1235d38e16a32"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96fc87ddf569181827458ec5ad8fa446c4690cffacda66667de780f9fcefd44d"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec98e2c7da7631f0811730303abc4bdfe70d013f7a11e174a2ccd5612a7c59"}, + {file = "lru_dict-1.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6bba2863060caeaedd8386b0c8ee9a7ce4d57a7cb80ceeddf440b4eff2d013ba"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c497fb60279f1e1d7dfbe150b1b069eaa43f7e172dab03f206282f4994676c5"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9509d817a47597988615c1a322580c10100acad10c98dfcf3abb41e0e5877f"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0213ab4e3d9a8d386c18e485ad7b14b615cb6f05df6ef44fb2a0746c6ea9278b"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50fbd69cd3287196796ab4d50e4cc741eb5b5a01f89d8e930df08da3010c385"}, + {file = "lru_dict-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5247d1f011f92666010942434020ddc5a60951fefd5d12a594f0e5d9f43e3b3b"}, ] [package.extras] @@ -1982,122 +1926,106 @@ test = ["pytest"] [[package]] name = "lxml" -version = "4.9.3" +version = "5.1.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" files = [ - {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, - {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, - {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, - {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, - {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, - {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, + {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, + {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, + {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, + {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, + {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, + {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, + {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, + {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, + {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, + {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, + {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, + {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, + {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, + {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, + {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, + {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, + {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.35)"] +source = ["Cython (>=3.0.7)"] [[package]] name = "mako" -version = "1.2.4" +version = "1.3.2" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, - {file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, + {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, + {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, ] [package.dependencies] @@ -2110,135 +2038,143 @@ testing = ["pytest"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] name = "matplotlib" -version = "3.8.0" +version = "3.8.2" description = "Python plotting package" -category = "main" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a"}, - {file = "matplotlib-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39"}, - {file = "matplotlib-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c"}, - {file = "matplotlib-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732"}, - {file = "matplotlib-3.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900"}, - {file = "matplotlib-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196"}, - {file = "matplotlib-3.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e"}, - {file = "matplotlib-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06"}, - {file = "matplotlib-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087"}, - {file = "matplotlib-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99"}, - {file = "matplotlib-3.8.0.tar.gz", hash = "sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, + {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, + {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, + {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, + {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, + {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, + {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, + {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, + {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, + {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, ] [package.dependencies] contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" -kiwisolver = ">=1.0.1" +kiwisolver = ">=1.3.1" numpy = ">=1.21,<2" packaging = ">=20.0" -pillow = ">=6.2.0" +pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" -setuptools_scm = ">=7" [[package]] name = "minio" -version = "7.1.17" +version = "7.2.3" description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" -category = "main" optional = false python-versions = "*" files = [ - {file = "minio-7.1.17-py3-none-any.whl", hash = "sha256:0aa525d77a3bc61378444c2400b0ba2685ad4cd6ecb3fba4141a0d0765e25f40"}, - {file = "minio-7.1.17.tar.gz", hash = "sha256:b0b687c1ec9be422a1f8b04c65fb8e43a1c090f9508178db57c434a17341c404"}, + {file = "minio-7.2.3-py3-none-any.whl", hash = "sha256:e6b5ce0a9b4368da50118c3f0c4df5dbf33885d44d77fce6c0aa1c485e6af7a1"}, + {file = "minio-7.2.3.tar.gz", hash = "sha256:4971dfb1a71eeefd38e1ce2dc7edc4e6eb0f07f1c1d6d70c15457e3280cfc4b9"}, ] [package.dependencies] +argon2-cffi = "*" certifi = "*" +pycryptodome = "*" +typing-extensions = "*" urllib3 = "*" [[package]] name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2322,7 +2258,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2332,58 +2267,55 @@ files = [ [[package]] name = "networkx" -version = "3.1" +version = "3.2.1" description = "Python package for creating and manipulating graphs and networks" -category = "main" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, - {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, + {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, + {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, ] [package.extras] -default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] -developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] -test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "nibabel" -version = "5.1.0" +version = "5.2.0" description = "Access a multitude of neuroimaging data formats" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "nibabel-5.1.0-py3-none-any.whl", hash = "sha256:b3deb8130c835b9d26e80880b0d5e443d9e3f30972b3b0302dd2fafa3ca629f8"}, - {file = "nibabel-5.1.0.tar.gz", hash = "sha256:ce73ca5e957209e7219a223cb71f77235c9df2acf4d3f27f861ba38e9481ac53"}, + {file = "nibabel-5.2.0-py3-none-any.whl", hash = "sha256:77724af6e29fd9c4173702e4d031e7d8c45b5963887905a0f90edab880381b7f"}, + {file = "nibabel-5.2.0.tar.gz", hash = "sha256:3df8f1ab981d1bd92f4331d565528d126ab9717fdbd4cfe68f43fcd1c2bf3f52"}, ] [package.dependencies] -numpy = ">=1.19" +numpy = ">=1.20" packaging = ">=17" [package.extras] -all = ["nibabel[dev,dicomfs,doc,minc2,spm,style,test,zstd]"] -dev = ["gitpython", "nibabel[style]", "twine"] +all = ["nibabel[dicomfs,minc2,spm,zstd]"] +dev = ["tox"] dicom = ["pydicom (>=1.0.0)"] dicomfs = ["nibabel[dicom]", "pillow"] -doc = ["matplotlib (>=1.5.3)", "numpydoc", "sphinx (>=5.3,<6.0)", "texext", "tomli"] -doctest = ["nibabel[doc,test]"] +doc = ["matplotlib (>=1.5.3)", "numpydoc", "sphinx", "texext", "tomli"] +doctest = ["tox"] minc2 = ["h5py"] spm = ["scipy"] -style = ["blue", "flake8", "isort"] -test = ["coverage", "pytest (!=5.3.4)", "pytest-cov", "pytest-doctestplus", "pytest-httpserver", "pytest-xdist"] -typing = ["importlib-resources", "mypy", "pydicom", "pytest", "pyzstd", "types-pillow", "types-setuptools"] +style = ["tox"] +test = ["pytest", "pytest-cov", "pytest-doctestplus", "pytest-httpserver", "pytest-xdist"] +typing = ["tox"] zstd = ["pyzstd (>=0.14.3)"] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -2396,131 +2328,136 @@ setuptools = "*" [[package]] name = "numpy" -version = "1.25.2" +version = "1.26.3" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.9" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"}, + {file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"}, + {file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"}, + {file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"}, + {file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"}, + {file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"}, + {file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"}, + {file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"}, + {file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"}, + {file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"}, + {file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"}, + {file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"}, + {file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"}, + {file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"}, + {file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"}, + {file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"}, + {file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"}, + {file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"}, + {file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"}, + {file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"}, + {file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"}, + {file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"}, + {file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"}, ] [[package]] name = "opencv-python" -version = "4.8.1.78" +version = "4.9.0.80" description = "Wrapper package for OpenCV python bindings." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-4.8.1.78.tar.gz", hash = "sha256:cc7adbbcd1112877a39274106cb2752e04984bc01a031162952e97450d6117f6"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:91d5f6f5209dc2635d496f6b8ca6573ecdad051a09e6b5de4c399b8e673c60da"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31f47e05447da8b3089faa0a07ffe80e114c91ce0b171e6424f9badbd1c5cd"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9814beca408d3a0eca1bae7e3e5be68b07c17ecceb392b94170881216e09b319"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c406bdb41eb21ea51b4e90dfbc989c002786c3f601c236a99c59a54670a394"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:a7aac3900fbacf55b551e7b53626c3dad4c71ce85643645c43e91fcb19045e47"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956"}, + {file = "opencv-python-4.9.0.80.tar.gz", hash = "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0"}, ] [package.dependencies] numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, + {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, ] [[package]] name = "orjson" -version = "3.9.9" +version = "3.9.12" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "orjson-3.9.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f28090060a31f4d11221f9ba48b2273b0d04b702f4dcaa197c38c64ce639cc51"}, - {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8038ba245d0c0a6337cfb6747ea0c51fe18b0cf1a4bc943d530fd66799fae33d"}, - {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:543b36df56db195739c70d645ecd43e49b44d5ead5f8f645d2782af118249b37"}, - {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e7877256b5092f1e4e48fc0f1004728dc6901e7a4ffaa4acb0a9578610aa4ce"}, - {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b83e0d8ba4ca88b894c3e00efc59fe6d53d9ffb5dbbb79d437a466fc1a513d"}, - {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef06431f021453a47a9abb7f7853f04f031d31fbdfe1cc83e3c6aadde502cce"}, - {file = "orjson-3.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0a1a4d9e64597e550428ba091e51a4bcddc7a335c8f9297effbfa67078972b5c"}, - {file = "orjson-3.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:879d2d1f6085c9c0831cec6716c63aaa89e41d8e036cabb19a315498c173fcc6"}, - {file = "orjson-3.9.9-cp310-none-win32.whl", hash = "sha256:d3f56e41bc79d30fdf077073072f2377d2ebf0b946b01f2009ab58b08907bc28"}, - {file = "orjson-3.9.9-cp310-none-win_amd64.whl", hash = "sha256:ab7bae2b8bf17620ed381e4101aeeb64b3ba2a45fc74c7617c633a923cb0f169"}, - {file = "orjson-3.9.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:31d676bc236f6e919d100fb85d0a99812cff1ebffaa58106eaaec9399693e227"}, - {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:678ffb5c0a6b1518b149cc328c610615d70d9297e351e12c01d0beed5d65360f"}, - {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71b0cc21f2c324747bc77c35161e0438e3b5e72db6d3b515310457aba743f7f"}, - {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae72621f216d1d990468291b1ec153e1b46e0ed188a86d54e0941f3dabd09ee8"}, - {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:512e5a41af008e76451f5a344941d61f48dddcf7d7ddd3073deb555de64596a6"}, - {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f89dc338a12f4357f5bf1b098d3dea6072fb0b643fd35fec556f4941b31ae27"}, - {file = "orjson-3.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:957a45fb201c61b78bcf655a16afbe8a36c2c27f18a998bd6b5d8a35e358d4ad"}, - {file = "orjson-3.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1c01cf4b8e00c7e98a0a7cf606a30a26c32adf2560be2d7d5d6766d6f474b31"}, - {file = "orjson-3.9.9-cp311-none-win32.whl", hash = "sha256:397a185e5dd7f8ebe88a063fe13e34d61d394ebb8c70a443cee7661b9c89bda7"}, - {file = "orjson-3.9.9-cp311-none-win_amd64.whl", hash = "sha256:24301f2d99d670ded4fb5e2f87643bc7428a54ba49176e38deb2887e42fe82fb"}, - {file = "orjson-3.9.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd55ea5cce3addc03f8fb0705be0cfed63b048acc4f20914ce5e1375b15a293b"}, - {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b28c1a65cd13fff5958ab8b350f0921121691464a7a1752936b06ed25c0c7b6e"}, - {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b97a67c47840467ccf116136450c50b6ed4e16a8919c81a4b4faef71e0a2b3f4"}, - {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75b805549cbbcb963e9c9068f1a05abd0ea4c34edc81f8d8ef2edb7e139e5b0f"}, - {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5424ecbafe57b2de30d3b5736c5d5835064d522185516a372eea069b92786ba6"}, - {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d2cd6ef4726ef1b8c63e30d8287225a383dbd1de3424d287b37c1906d8d2855"}, - {file = "orjson-3.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c959550e0705dc9f59de8fca1a316da0d9b115991806b217c82931ac81d75f74"}, - {file = "orjson-3.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ece2d8ed4c34903e7f1b64fb1e448a00e919a4cdb104fc713ad34b055b665fca"}, - {file = "orjson-3.9.9-cp312-none-win_amd64.whl", hash = "sha256:f708ca623287186e5876256cb30599308bce9b2757f90d917b7186de54ce6547"}, - {file = "orjson-3.9.9-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:335406231f9247f985df045f0c0c8f6b6d5d6b3ff17b41a57c1e8ef1a31b4d04"}, - {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d9b5440a5d215d9e1cfd4aee35fd4101a8b8ceb8329f549c16e3894ed9f18b5"}, - {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e98ca450cb4fb176dd572ce28c6623de6923752c70556be4ef79764505320acb"}, - {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3bf6ca6bce22eb89dd0650ef49c77341440def966abcb7a2d01de8453df083a"}, - {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb50d869b3c97c7c5187eda3759e8eb15deb1271d694bc5d6ba7040db9e29036"}, - {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fcf06c69ccc78e32d9f28aa382ab2ab08bf54b696dbe00ee566808fdf05da7d"}, - {file = "orjson-3.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9a4402e7df1b5c9a4c71c7892e1c8f43f642371d13c73242bda5964be6231f95"}, - {file = "orjson-3.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b20becf50d4aec7114dc902b58d85c6431b3a59b04caa977e6ce67b6fee0e159"}, - {file = "orjson-3.9.9-cp38-none-win32.whl", hash = "sha256:1f352117eccac268a59fedac884b0518347f5e2b55b9f650c2463dd1e732eb61"}, - {file = "orjson-3.9.9-cp38-none-win_amd64.whl", hash = "sha256:c4eb31a8e8a5e1d9af5aa9e247c2a52ad5cf7e968aaa9aaefdff98cfcc7f2e37"}, - {file = "orjson-3.9.9-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4a308aeac326c2bafbca9abbae1e1fcf682b06e78a54dad0347b760525838d85"}, - {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e159b97f5676dcdac0d0f75ec856ef5851707f61d262851eb41a30e8fadad7c9"}, - {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f692e7aabad92fa0fff5b13a846fb586b02109475652207ec96733a085019d80"}, - {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cffb77cf0cd3cbf20eb603f932e0dde51b45134bdd2d439c9f57924581bb395b"}, - {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c63eca397127ebf46b59c9c1fb77b30dd7a8fc808ac385e7a58a7e64bae6e106"}, - {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f0c024a75e8ba5d9101facb4fb5a028cdabe3cdfe081534f2a9de0d5062af2"}, - {file = "orjson-3.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8cba20c9815c2a003b8ca4429b0ad4aa87cb6649af41365821249f0fd397148e"}, - {file = "orjson-3.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:906cac73b7818c20cf0f6a7dde5a6f009c52aecc318416c7af5ea37f15ca7e66"}, - {file = "orjson-3.9.9-cp39-none-win32.whl", hash = "sha256:50232572dd300c49f134838c8e7e0917f29a91f97dbd608d23f2895248464b7f"}, - {file = "orjson-3.9.9-cp39-none-win_amd64.whl", hash = "sha256:920814e02e3dd7af12f0262bbc18b9fe353f75a0d0c237f6a67d270da1a1bb44"}, - {file = "orjson-3.9.9.tar.gz", hash = "sha256:02e693843c2959befdd82d1ebae8b05ed12d1cb821605d5f9fe9f98ca5c9fd2b"}, + {file = "orjson-3.9.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6b4e2bed7d00753c438e83b613923afdd067564ff7ed696bfe3a7b073a236e07"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd1b8ec63f0bf54a50b498eedeccdca23bd7b658f81c524d18e410c203189365"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab8add018a53665042a5ae68200f1ad14c7953fa12110d12d41166f111724656"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12756a108875526b76e505afe6d6ba34960ac6b8c5ec2f35faf73ef161e97e07"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:890e7519c0c70296253660455f77e3a194554a3c45e42aa193cdebc76a02d82b"}, + {file = "orjson-3.9.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d664880d7f016efbae97c725b243b33c2cbb4851ddc77f683fd1eec4a7894146"}, + {file = "orjson-3.9.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cfdaede0fa5b500314ec7b1249c7e30e871504a57004acd116be6acdda3b8ab3"}, + {file = "orjson-3.9.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6492ff5953011e1ba9ed1bf086835fd574bd0a3cbe252db8e15ed72a30479081"}, + {file = "orjson-3.9.12-cp310-none-win32.whl", hash = "sha256:29bf08e2eadb2c480fdc2e2daae58f2f013dff5d3b506edd1e02963b9ce9f8a9"}, + {file = "orjson-3.9.12-cp310-none-win_amd64.whl", hash = "sha256:0fc156fba60d6b50743337ba09f052d8afc8b64595112996d22f5fce01ab57da"}, + {file = "orjson-3.9.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2849f88a0a12b8d94579b67486cbd8f3a49e36a4cb3d3f0ab352c596078c730c"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3186b18754befa660b31c649a108a915493ea69b4fc33f624ed854ad3563ac65"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbbf313c9fb9d4f6cf9c22ced4b6682230457741daeb3d7060c5d06c2e73884a"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99e8cd005b3926c3db9b63d264bd05e1bf4451787cc79a048f27f5190a9a0311"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59feb148392d9155f3bfed0a2a3209268e000c2c3c834fb8fe1a6af9392efcbf"}, + {file = "orjson-3.9.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4ae815a172a1f073b05b9e04273e3b23e608a0858c4e76f606d2d75fcabde0c"}, + {file = "orjson-3.9.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed398f9a9d5a1bf55b6e362ffc80ac846af2122d14a8243a1e6510a4eabcb71e"}, + {file = "orjson-3.9.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d3cfb76600c5a1e6be91326b8f3b83035a370e727854a96d801c1ea08b708073"}, + {file = "orjson-3.9.12-cp311-none-win32.whl", hash = "sha256:a2b6f5252c92bcab3b742ddb3ac195c0fa74bed4319acd74f5d54d79ef4715dc"}, + {file = "orjson-3.9.12-cp311-none-win_amd64.whl", hash = "sha256:c95488e4aa1d078ff5776b58f66bd29d628fa59adcb2047f4efd3ecb2bd41a71"}, + {file = "orjson-3.9.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6ce2062c4af43b92b0221ed4f445632c6bf4213f8a7da5396a122931377acd9"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:950951799967558c214cd6cceb7ceceed6f81d2c3c4135ee4a2c9c69f58aa225"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2dfaf71499d6fd4153f5c86eebb68e3ec1bf95851b030a4b55c7637a37bbdee4"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:659a8d7279e46c97661839035a1a218b61957316bf0202674e944ac5cfe7ed83"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af17fa87bccad0b7f6fd8ac8f9cbc9ee656b4552783b10b97a071337616db3e4"}, + {file = "orjson-3.9.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd52dec9eddf4c8c74392f3fd52fa137b5f2e2bed1d9ae958d879de5f7d7cded"}, + {file = "orjson-3.9.12-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:640e2b5d8e36b970202cfd0799d11a9a4ab46cf9212332cd642101ec952df7c8"}, + {file = "orjson-3.9.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:daa438bd8024e03bcea2c5a92cd719a663a58e223fba967296b6ab9992259dbf"}, + {file = "orjson-3.9.12-cp312-none-win_amd64.whl", hash = "sha256:1bb8f657c39ecdb924d02e809f992c9aafeb1ad70127d53fb573a6a6ab59d549"}, + {file = "orjson-3.9.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f4098c7674901402c86ba6045a551a2ee345f9f7ed54eeffc7d86d155c8427e5"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5586a533998267458fad3a457d6f3cdbddbcce696c916599fa8e2a10a89b24d3"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54071b7398cd3f90e4bb61df46705ee96cb5e33e53fc0b2f47dbd9b000e238e1"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:67426651faa671b40443ea6f03065f9c8e22272b62fa23238b3efdacd301df31"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a0cd56e8ee56b203abae7d482ac0d233dbfb436bb2e2d5cbcb539fe1200a312"}, + {file = "orjson-3.9.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a84a0c3d4841a42e2571b1c1ead20a83e2792644c5827a606c50fc8af7ca4bee"}, + {file = "orjson-3.9.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:09d60450cda3fa6c8ed17770c3a88473a16460cd0ff2ba74ef0df663b6fd3bb8"}, + {file = "orjson-3.9.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bc82a4db9934a78ade211cf2e07161e4f068a461c1796465d10069cb50b32a80"}, + {file = "orjson-3.9.12-cp38-none-win32.whl", hash = "sha256:61563d5d3b0019804d782137a4f32c72dc44c84e7d078b89d2d2a1adbaa47b52"}, + {file = "orjson-3.9.12-cp38-none-win_amd64.whl", hash = "sha256:410f24309fbbaa2fab776e3212a81b96a1ec6037259359a32ea79fbccfcf76aa"}, + {file = "orjson-3.9.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e773f251258dd82795fd5daeac081d00b97bacf1548e44e71245543374874bcf"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b159baecfda51c840a619948c25817d37733a4d9877fea96590ef8606468b362"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:975e72e81a249174840d5a8df977d067b0183ef1560a32998be340f7e195c730"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06e42e899dde61eb1851a9fad7f1a21b8e4be063438399b63c07839b57668f6c"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c157e999e5694475a5515942aebeed6e43f7a1ed52267c1c93dcfde7d78d421"}, + {file = "orjson-3.9.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dde1bc7c035f2d03aa49dc8642d9c6c9b1a81f2470e02055e76ed8853cfae0c3"}, + {file = "orjson-3.9.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b0e9d73cdbdad76a53a48f563447e0e1ce34bcecef4614eb4b146383e6e7d8c9"}, + {file = "orjson-3.9.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:96e44b21fe407b8ed48afbb3721f3c8c8ce17e345fbe232bd4651ace7317782d"}, + {file = "orjson-3.9.12-cp39-none-win32.whl", hash = "sha256:cbd0f3555205bf2a60f8812133f2452d498dbefa14423ba90fe89f32276f7abf"}, + {file = "orjson-3.9.12-cp39-none-win_amd64.whl", hash = "sha256:03ea7ee7e992532c2f4a06edd7ee1553f0644790553a118e003e3c405add41fa"}, + {file = "orjson-3.9.12.tar.gz", hash = "sha256:da908d23a3b3243632b523344403b128722a5f45e278a8343c2bb67538dff0e4"}, ] [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2530,136 +2467,80 @@ files = [ [[package]] name = "pandas" -version = "2.1.0" -description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:40dd20439ff94f1b2ed55b393ecee9cb6f3b08104c2c40b0cb7186a2f0046242"}, - {file = "pandas-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f38e4fedeba580285eaac7ede4f686c6701a9e618d8a857b138a126d067f2f"}, - {file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e6a0fe052cf27ceb29be9429428b4918f3740e37ff185658f40d8702f0b3e09"}, - {file = "pandas-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d81e1813191070440d4c7a413cb673052b3b4a984ffd86b8dd468c45742d3cc"}, - {file = "pandas-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eb20252720b1cc1b7d0b2879ffc7e0542dd568f24d7c4b2347cb035206936421"}, - {file = "pandas-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:38f74ef7ebc0ffb43b3d633e23d74882bce7e27bfa09607f3c5d3e03ffd9a4a5"}, - {file = "pandas-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cda72cc8c4761c8f1d97b169661f23a86b16fdb240bdc341173aee17e4d6cedd"}, - {file = "pandas-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d97daeac0db8c993420b10da4f5f5b39b01fc9ca689a17844e07c0a35ac96b4b"}, - {file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c58b1113892e0c8078f006a167cc210a92bdae23322bb4614f2f0b7a4b510f"}, - {file = "pandas-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629124923bcf798965b054a540f9ccdfd60f71361255c81fa1ecd94a904b9dd3"}, - {file = "pandas-2.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:70cf866af3ab346a10debba8ea78077cf3a8cd14bd5e4bed3d41555a3280041c"}, - {file = "pandas-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d53c8c1001f6a192ff1de1efe03b31a423d0eee2e9e855e69d004308e046e694"}, - {file = "pandas-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86f100b3876b8c6d1a2c66207288ead435dc71041ee4aea789e55ef0e06408cb"}, - {file = "pandas-2.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28f330845ad21c11db51e02d8d69acc9035edfd1116926ff7245c7215db57957"}, - {file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9a6ccf0963db88f9b12df6720e55f337447aea217f426a22d71f4213a3099a6"}, - {file = "pandas-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d99e678180bc59b0c9443314297bddce4ad35727a1a2656dbe585fd78710b3b9"}, - {file = "pandas-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b31da36d376d50a1a492efb18097b9101bdbd8b3fbb3f49006e02d4495d4c644"}, - {file = "pandas-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0164b85937707ec7f70b34a6c3a578dbf0f50787f910f21ca3b26a7fd3363437"}, - {file = "pandas-2.1.0.tar.gz", hash = "sha256:62c24c7fc59e42b775ce0679cfa7b14a5f9bfb7643cfbe708c960699e05fb918"}, -] - -[package.dependencies] -numpy = {version = ">=1.23.2", markers = "python_version >= \"3.11\""} -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.1" - -[package.extras] -all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] -aws = ["s3fs (>=2022.05.0)"] -clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] -compression = ["zstandard (>=0.17.0)"] -computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2022.05.0)"] -gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] -hdf5 = ["tables (>=3.7.0)"] -html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] -mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] -spss = ["pyreadstat (>=1.1.5)"] -sql-other = ["SQLAlchemy (>=1.4.36)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.8.0)"] - -[[package]] -name = "pandas" -version = "2.1.1" +version = "2.2.0" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"}, - {file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"}, - {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"}, - {file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"}, - {file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"}, - {file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"}, - {file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"}, - {file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"}, - {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"}, - {file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"}, - {file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"}, - {file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"}, - {file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"}, - {file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"}, - {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"}, - {file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"}, - {file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"}, - {file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"}, - {file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"}, - {file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"}, - {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"}, - {file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"}, - {file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"}, - {file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"}, - {file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"}, + {file = "pandas-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670"}, + {file = "pandas-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a"}, + {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18"}, + {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e"}, + {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5"}, + {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1"}, + {file = "pandas-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430"}, + {file = "pandas-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5"}, + {file = "pandas-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b"}, + {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f"}, + {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88"}, + {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3"}, + {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71"}, + {file = "pandas-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9"}, + {file = "pandas-2.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc"}, + {file = "pandas-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1"}, + {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440"}, + {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106"}, + {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e"}, + {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042"}, + {file = "pandas-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30"}, + {file = "pandas-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d"}, + {file = "pandas-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab"}, + {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a"}, + {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a"}, + {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd"}, + {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7"}, + {file = "pandas-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae"}, + {file = "pandas-2.2.0.tar.gz", hash = "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2"}, ] [package.dependencies] numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" -tzdata = ">=2022.1" +tzdata = ">=2022.7" [package.extras] -all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] -aws = ["s3fs (>=2022.05.0)"] -clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] -compression = ["zstandard (>=0.17.0)"] -computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2022.05.0)"] -gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] -hdf5 = ["tables (>=3.7.0)"] -html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] -mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] -spss = ["pyreadstat (>=1.1.5)"] -sql-other = ["SQLAlchemy (>=1.4.36)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.8.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] [[package]] name = "parsimonious" version = "0.9.0" description = "(Soon to be) the fastest pure-Python PEG parser I could muster" -category = "main" optional = false python-versions = "*" files = [ @@ -2671,21 +2552,19 @@ regex = ">=2022.3.15" [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "pgpy" version = "0.6.0" description = "Pretty Good Privacy for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2698,82 +2577,98 @@ pyasn1 = "*" [[package]] name = "pillow" -version = "10.0.1" +version = "10.2.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, - {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, - {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, - {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, - {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, - {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, - {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, - {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "3.11.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -2782,14 +2677,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -2798,14 +2692,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.4.0" +version = "3.6.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, - {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, + {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, + {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, ] [package.dependencies] @@ -2817,32 +2710,28 @@ virtualenv = ">=20.10.0" [[package]] name = "protobuf" -version = "4.24.4" +version = "4.25.2" description = "" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "protobuf-4.24.4-cp310-abi3-win32.whl", hash = "sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb"}, - {file = "protobuf-4.24.4-cp310-abi3-win_amd64.whl", hash = "sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085"}, - {file = "protobuf-4.24.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e"}, - {file = "protobuf-4.24.4-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9"}, - {file = "protobuf-4.24.4-cp37-cp37m-win32.whl", hash = "sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46"}, - {file = "protobuf-4.24.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37"}, - {file = "protobuf-4.24.4-cp38-cp38-win32.whl", hash = "sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe"}, - {file = "protobuf-4.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b"}, - {file = "protobuf-4.24.4-cp39-cp39-win32.whl", hash = "sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd"}, - {file = "protobuf-4.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b"}, - {file = "protobuf-4.24.4-py3-none-any.whl", hash = "sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92"}, - {file = "protobuf-4.24.4.tar.gz", hash = "sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667"}, + {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, + {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, + {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, + {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, + {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, + {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, + {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, + {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, + {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, ] [[package]] name = "psycopg2" version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2861,21 +2750,19 @@ files = [ [[package]] name = "pyasn1" -version = "0.5.0" +version = "0.5.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"}, - {file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"}, + {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, + {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] [[package]] name = "pycocotools" version = "2.0.7" description = "Official APIs for the MS-COCO dataset" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2909,7 +2796,6 @@ numpy = "*" name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2919,90 +2805,88 @@ files = [ [[package]] name = "pycryptodome" -version = "3.19.0" +version = "3.20.0" description = "Cryptographic library for Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"}, - {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] [[package]] name = "pydantic" -version = "1.10.13" +version = "1.10.14" description = "Data validation and settings management using python type hints" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, + {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, + {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, + {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, + {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, + {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, + {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, + {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, + {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, ] [package.dependencies] @@ -3016,7 +2900,6 @@ email = ["email-validator (>=1.0.3)"] name = "pyparsing" version = "3.1.1" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" optional = false python-versions = ">=3.6.8" files = [ @@ -3029,14 +2912,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.4.2" +version = "7.4.4" description = "pytest: simple powerful testing with Python" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -3054,7 +2936,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -3067,14 +2948,13 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -3082,32 +2962,29 @@ cli = ["click (>=5.0)"] [[package]] name = "pytz" -version = "2023.3.post1" +version = "2023.4" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, + {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, ] [[package]] name = "pyunormalize" -version = "15.0.0" +version = "15.1.0" description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent from the Python core Unicode database." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "pyunormalize-15.0.0.tar.gz", hash = "sha256:e63fdba0d85ea04579dde2fc29a072dba773dcae600b04faf6cc90714c8b1302"}, + {file = "pyunormalize-15.1.0.tar.gz", hash = "sha256:cf4a87451a0f1cb76911aa97f432f4579e1f564a2f0c84ce488c73a73901b6c1"}, ] [[package]] name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "main" optional = false python-versions = "*" files = [ @@ -3131,7 +3008,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -3140,6 +3016,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3147,8 +3024,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3165,6 +3049,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3172,6 +3057,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3179,14 +3065,13 @@ files = [ [[package]] name = "referencing" -version = "0.30.2" +version = "0.33.0" description = "JSON Referencing + Python" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.30.2-py3-none-any.whl", hash = "sha256:449b6669b6121a9e96a7f9e410b245d471e8d48964c67113ce9afe50c8dd7bdf"}, - {file = "referencing-0.30.2.tar.gz", hash = "sha256:794ad8003c65938edcdbc027f1933215e0d0ccc0291e3ce20a4d87432b59efc0"}, + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, ] [package.dependencies] @@ -3195,107 +3080,110 @@ rpds-py = ">=0.7.0" [[package]] name = "regex" -version = "2023.10.3" +version = "2023.12.25" description = "Alternative regular expression module, to replace re." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"}, + {file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"}, + {file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"}, + {file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"}, + {file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"}, + {file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"}, + {file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"}, + {file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"}, + {file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"}, + {file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"}, + {file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"}, + {file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"}, + {file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"}, + {file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"}, + {file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"}, + {file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"}, + {file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"}, + {file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"}, + {file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"}, + {file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"}, + {file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"}, + {file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"}, + {file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"}, + {file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"}, + {file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"}, + {file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"}, + {file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"}, + {file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"}, + {file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"}, + {file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"}, + {file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"}, + {file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"}, ] [[package]] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3315,194 +3203,183 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rlp" -version = "3.0.0" -description = "A package for Recursive Length Prefix encoding and decoding" -category = "main" +version = "4.0.0" +description = "rlp: A package for Recursive Length Prefix encoding and decoding" optional = false -python-versions = "*" +python-versions = ">=3.8, <4" files = [ - {file = "rlp-3.0.0-py2.py3-none-any.whl", hash = "sha256:d2a963225b3f26795c5b52310e0871df9824af56823d739511583ef459895a7d"}, - {file = "rlp-3.0.0.tar.gz", hash = "sha256:63b0465d2948cd9f01de449d7adfb92d207c1aef3982f20310f8009be4a507e8"}, + {file = "rlp-4.0.0-py3-none-any.whl", hash = "sha256:1747fd933e054e6d25abfe591be92e19a4193a56c93981c05bd0f84dfe279f14"}, + {file = "rlp-4.0.0.tar.gz", hash = "sha256:61a5541f86e4684ab145cb849a5929d2ced8222930a570b3941cf4af16b72a78"}, ] [package.dependencies] -eth-utils = ">=2.0.0,<3" +eth-utils = ">=2" [package.extras] -dev = ["Sphinx (>=1.6.5,<2)", "bumpversion (>=0.5.3,<1)", "flake8 (==3.4.1)", "hypothesis (==5.19.0)", "ipython", "pytest (>=6.2.5,<7)", "pytest-watch (>=4.1.0,<5)", "pytest-xdist", "setuptools (>=36.2.0)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.9.1,<3)", "twine", "wheel"] -doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9)"] -lint = ["flake8 (==3.4.1)"] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] rust-backend = ["rusty-rlp (>=0.2.1,<0.3)"] -test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] +test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "rpds-py" -version = "0.10.4" +version = "0.17.1" description = "Python bindings to Rust's persistent data structures (rpds)" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.4-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:e41824343c2c129599645373992b1ce17720bb8a514f04ff9567031e1c26951e"}, - {file = "rpds_py-0.10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b9d8884d58ea8801e5906a491ab34af975091af76d1a389173db491ee7e316bb"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5db93f9017b384a4f194e1d89e1ce82d0a41b1fafdbbd3e0c8912baf13f2950f"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c31ecfc53ac03dad4928a1712f3a2893008bfba1b3cde49e1c14ff67faae2290"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f92d2372ec992c82fd7c74aa21e2a1910b3dcdc6a7e6392919a138f21d528a3"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7ea49ddf51d5ec0c3cbd95190dd15e077a3153c8d4b22a33da43b5dd2b3c640"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c27942722cd5039bbf5098c7e21935a96243fed00ea11a9589f3c6c6424bd84"}, - {file = "rpds_py-0.10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08f07150c8ebbdbce1d2d51b8e9f4d588749a2af6a98035485ebe45c7ad9394e"}, - {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f3331a3684192659fa1090bf2b448db928152fcba08222e58106f44758ef25f7"}, - {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:efffa359cc69840c8793f0c05a7b663de6afa7b9078fa6c80309ee38b9db677d"}, - {file = "rpds_py-0.10.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86e8d6ff15fa7a9590c0addaf3ce52fb58bda4299cab2c2d0afa404db6848dab"}, - {file = "rpds_py-0.10.4-cp310-none-win32.whl", hash = "sha256:8f90fc6dd505867514c8b8ef68a712dc0be90031a773c1ae2ad469f04062daef"}, - {file = "rpds_py-0.10.4-cp310-none-win_amd64.whl", hash = "sha256:9f9184744fb800c9f28e155a5896ecb54816296ee79d5d1978be6a2ae60f53c4"}, - {file = "rpds_py-0.10.4-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:72e9b1e92830c876cd49565d8404e4dcc9928302d348ea2517bc3f9e3a873a2a"}, - {file = "rpds_py-0.10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3650eae998dc718960e90120eb45d42bd57b18b21b10cb9ee05f91bff2345d48"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f40413d2859737ce6d95c29ce2dde0ef7cdc3063b5830ae4342fef5922c3bba7"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b953d11b544ca5f2705bb77b177d8e17ab1bfd69e0fd99790a11549d2302258c"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28b4942ec7d9d6114c1e08cace0157db92ef674636a38093cab779ace5742d3a"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e0e2e01c5f61ddf47e3ed2d1fe1c9136e780ca6222d57a2517b9b02afd4710c"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:927e3461dae0c09b1f2e0066e50c1a9204f8a64a3060f596e9a6742d3b307785"}, - {file = "rpds_py-0.10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e69bbe0ede8f7fe2616e779421bbdb37f025c802335a90f6416e4d98b368a37"}, - {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc688a59c100f038fa9fec9e4ab457c2e2d1fca350fe7ea395016666f0d0a2dc"}, - {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ec001689402b9104700b50a005c2d3d0218eae90eaa8bdbbd776fe78fe8a74b7"}, - {file = "rpds_py-0.10.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:628fbb8be71a103499d10b189af7764996ab2634ed7b44b423f1e19901606e0e"}, - {file = "rpds_py-0.10.4-cp311-none-win32.whl", hash = "sha256:e3f9c9e5dd8eba4768e15f19044e1b5e216929a43a54b4ab329e103aed9f3eda"}, - {file = "rpds_py-0.10.4-cp311-none-win_amd64.whl", hash = "sha256:3bc561c183684636c0099f9c3fbab8c1671841942edbce784bb01b4707d17924"}, - {file = "rpds_py-0.10.4-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:36ff30385fb9fb3ac23a28bffdd4a230a5229ed5b15704b708b7c84bfb7fce51"}, - {file = "rpds_py-0.10.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db0589e0bf41ff6ce284ab045ca89f27be1adf19e7bce26c2e7de6739a70c18b"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c330cb125983c5d380fef4a4155248a276297c86d64625fdaf500157e1981c"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d230fddc60caced271cc038e43e6fb8f4dd6b2dbaa44ac9763f2d76d05b0365a"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a9e864ec051a58fdb6bb2e6da03942adb20273897bc70067aee283e62bbac4d"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e41d5b334e8de4bc3f38843f31b2afa9a0c472ebf73119d3fd55cde08974bdf"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bb3f3cb6072c73e6ec1f865d8b80419b599f1597acf33f63fbf02252aab5a03"}, - {file = "rpds_py-0.10.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:576d48e1e45c211e99fc02655ade65c32a75d3e383ccfd98ce59cece133ed02c"}, - {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b28b9668a22ca2cfca4433441ba9acb2899624a323787a509a3dc5fbfa79c49d"}, - {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ddbd113a37307638f94be5ae232a325155fd24dbfae2c56455da8724b471e7be"}, - {file = "rpds_py-0.10.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd0ad98c7d72b0e4cbfe89cdfa12cd07d2fd6ed22864341cdce12b318a383442"}, - {file = "rpds_py-0.10.4-cp312-none-win32.whl", hash = "sha256:2a97406d5e08b7095428f01dac0d3c091dc072351151945a167e7968d2755559"}, - {file = "rpds_py-0.10.4-cp312-none-win_amd64.whl", hash = "sha256:aab24b9bbaa3d49e666e9309556591aa00748bd24ea74257a405f7fed9e8b10d"}, - {file = "rpds_py-0.10.4-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6c5ca3eb817fb54bfd066740b64a2b31536eb8fe0b183dc35b09a7bd628ed680"}, - {file = "rpds_py-0.10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd37ab9a24021821b715478357af1cf369d5a42ac7405e83e5822be00732f463"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2573ec23ad3a59dd2bc622befac845695972f3f2d08dc1a4405d017d20a6c225"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:362faeae52dc6ccc50c0b6a01fa2ec0830bb61c292033f3749a46040b876f4ba"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40f6e53461b19ddbb3354fe5bcf3d50d4333604ae4bf25b478333d83ca68002c"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6090ba604ea06b525a231450ae5d343917a393cbf50423900dea968daf61d16f"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28e29dac59df890972f73c511948072897f512974714a803fe793635b80ff8c7"}, - {file = "rpds_py-0.10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f82abb5c5b83dc30e96be99ce76239a030b62a73a13c64410e429660a5602bfd"}, - {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a3628815fd170a64624001bfb4e28946fd515bd672e68a1902d9e0290186eaf3"}, - {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d37f27ad80f742ef82796af3fe091888864958ad0bc8bab03da1830fa00c6004"}, - {file = "rpds_py-0.10.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:255a23bded80605e9f3997753e3a4b89c9aec9efb07ec036b1ca81440efcc1a9"}, - {file = "rpds_py-0.10.4-cp38-none-win32.whl", hash = "sha256:049098dabfe705e9638c55a3321137a821399c50940041a6fcce267a22c70db2"}, - {file = "rpds_py-0.10.4-cp38-none-win_amd64.whl", hash = "sha256:aa45cc71bf23a3181b8aa62466b5a2b7b7fb90fdc01df67ca433cd4fce7ec94d"}, - {file = "rpds_py-0.10.4-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:3507c459767cf24c11e9520e2a37c89674266abe8e65453e5cb66398aa47ee7b"}, - {file = "rpds_py-0.10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2603e084054351cc65097da326570102c4c5bd07426ba8471ceaefdb0b642cc9"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0f1d336786cb62613c72c00578c98e5bb8cd57b49c5bae5d4ab906ca7872f98"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf032367f921201deaecf221d4cc895ea84b3decf50a9c73ee106f961885a0ad"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f050ceffd8c730c1619a16bbf0b9cd037dcdb94b54710928ba38c7bde67e4a4"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8709eb4ab477c533b7d0a76cd3065d7d95c9e25e6b9f6e27caeeb8c63e8799c9"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc20dadb102140dff63529e08ce6f9745dbd36e673ebb2b1c4a63e134bca81c2"}, - {file = "rpds_py-0.10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd7da2adc721ccf19ac7ec86cae3a4fcaba03d9c477d5bd64ded6e9bb817bf3f"}, - {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5dba1c11e089b526379e74f6c636202e4c5bad9a48c7416502b8a5b0d026c91"}, - {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ffd539d213c1ea2989ab92a5b9371ae7159c8c03cf2bcb9f2f594752f755ecd3"}, - {file = "rpds_py-0.10.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e791e3d13b14d0a7921804d0efe4d7bd15508bbcf8cb7a0c1ee1a27319a5f033"}, - {file = "rpds_py-0.10.4-cp39-none-win32.whl", hash = "sha256:2f2ac8bb01f705c5caaa7fe77ffd9b03f92f1b5061b94228f6ea5eaa0fca68ad"}, - {file = "rpds_py-0.10.4-cp39-none-win_amd64.whl", hash = "sha256:7c7ca791bedda059e5195cf7c6b77384657a51429357cdd23e64ac1d4973d6dc"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:9c7e7bd1fa1f535af71dfcd3700fc83a6dc261a1204f8f5327d8ffe82e52905d"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7089d8bfa8064b28b2e39f5af7bf12d42f61caed884e35b9b4ea9e6fb1175077"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1f191befea279cb9669b57be97ab1785781c8bab805900e95742ebfaa9cbf1d"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98c0aecf661c175ce9cb17347fc51a5c98c3e9189ca57e8fcd9348dae18541db"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81359911c3bb31c899c6a5c23b403bdc0279215e5b3bc0d2a692489fed38632"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:83da147124499fe41ed86edf34b4e81e951b3fe28edcc46288aac24e8a5c8484"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49db6c0a0e6626c2b97f5e7f8f7074da21cbd8ec73340c25e839a2457c007efa"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:125776d5db15162fdd9135372bef7fe4fb7c5f5810cf25898eb74a06a0816aec"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:32819b662e3b4c26355a4403ea2f60c0a00db45b640fe722dd12db3d2ef807fb"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3bd38b80491ef9686f719c1ad3d24d14fbd0e069988fdd4e7d1a6ffcdd7f4a13"}, - {file = "rpds_py-0.10.4-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2e79eeeff8394284b09577f36316d410525e0cf0133abb3de10660e704d3d38e"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3e37f1f134037601eb4b1f46854194f0cc082435dac2ee3de11e51529f7831f2"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ba3246c60303eab3d0e562addf25a983d60bddc36f4d1edc2510f056d19df255"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9123ba0f3f98ff79780eebca9984a2b525f88563844b740f94cffb9099701230"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d98802b78093c7083cc51f83da41a5be5a57d406798c9f69424bd75f8ae0812a"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58bae860d1d116e6b4e1aad0cdc48a187d5893994f56d26db0c5534df7a47afd"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd7e62e7d5bcfa38a62d8397fba6d0428b970ab7954c2197501cd1624f7f0bbb"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83f5228459b84fa6279e4126a53abfdd73cd9cc183947ee5084153880f65d7"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bcb1abecd998a72ad4e36a0fca93577fd0c059a6aacc44f16247031b98f6ff4"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:9e7b3ad9f53ea9e085b3d27286dd13f8290969c0a153f8a52c8b5c46002c374b"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:cbec8e43cace64e63398155dc585dc479a89fef1e57ead06c22d3441e1bd09c3"}, - {file = "rpds_py-0.10.4-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ad21c60fc880204798f320387164dcacc25818a7b4ec2a0bf6b6c1d57b007d23"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:6baea8a4f6f01e69e75cfdef3edd4a4d1c4b56238febbdf123ce96d09fbff010"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:94876c21512535955a960f42a155213315e6ab06a4ce8ce372341a2a1b143eeb"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cb55454a20d1b935f9eaab52e6ceab624a2efd8b52927c7ae7a43e02828dbe0"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13cbd79ccedc6b39c279af31ebfb0aec0467ad5d14641ddb15738bf6e4146157"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00a88003db3cc953f8656b59fc9af9d0637a1fb93c235814007988f8c153b2f2"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0f7f77a77c37159c9f417b8dd847f67a29e98c6acb52ee98fc6b91efbd1b2b6"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70563a1596d2e0660ca2cebb738443437fc0e38597e7cbb276de0a7363924a52"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3ece9aa6d07e18c966f14b4352a4c6f40249f6174d3d2c694c1062e19c6adbb"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d5ad7b1a1f6964d19b1a8acfc14bf7864f39587b3e25c16ca04f6cd1815026b3"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:60018626e637528a1fa64bb3a2b3e46ab7bf672052316d61c3629814d5e65052"}, - {file = "rpds_py-0.10.4-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ae8a32ab77a84cc870bbfb60645851ca0f7d58fd251085ad67464b1445d632ca"}, - {file = "rpds_py-0.10.4.tar.gz", hash = "sha256:18d5ff7fbd305a1d564273e9eb22de83ae3cd9cd6329fddc8f12f6428a711a6a"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] [[package]] name = "ruamel-yaml" -version = "0.17.35" +version = "0.18.5" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" -category = "main" optional = false -python-versions = ">=3" +python-versions = ">=3.7" files = [ - {file = "ruamel.yaml-0.17.35-py3-none-any.whl", hash = "sha256:b105e3e6fc15b41fdb201ba1b95162ae566a4ef792b9f884c46b4ccc5513a87a"}, - {file = "ruamel.yaml-0.17.35.tar.gz", hash = "sha256:801046a9caacb1b43acc118969b49b96b65e8847f29029563b29ac61d02db61b"}, + {file = "ruamel.yaml-0.18.5-py3-none-any.whl", hash = "sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada"}, + {file = "ruamel.yaml-0.18.5.tar.gz", hash = "sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e"}, ] [package.dependencies] "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} [package.extras] -docs = ["ryd"] +docs = ["mercurial (>5.7)", "ryd"] jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] [[package]] name = "ruamel-yaml-clib" version = "0.2.8" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" -category = "main" optional = false python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, @@ -3511,110 +3388,83 @@ files = [ [[package]] name = "s3transfer" -version = "0.7.0" +version = "0.10.0" description = "An Amazon S3 Transfer Manager" -category = "main" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "s3transfer-0.7.0-py3-none-any.whl", hash = "sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a"}, - {file = "s3transfer-0.7.0.tar.gz", hash = "sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e"}, + {file = "s3transfer-0.10.0-py3-none-any.whl", hash = "sha256:3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e"}, + {file = "s3transfer-0.10.0.tar.gz", hash = "sha256:d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b"}, ] [package.dependencies] -botocore = ">=1.12.36,<2.0a.0" +botocore = ">=1.33.2,<2.0a.0" [package.extras] -crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "scipy" -version = "1.11.3" +version = "1.12.0" description = "Fundamental algorithms for scientific computing in Python" -category = "main" optional = false -python-versions = "<3.13,>=3.9" +python-versions = ">=3.9" files = [ - {file = "scipy-1.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0"}, - {file = "scipy-1.11.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3"}, - {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc"}, - {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1"}, - {file = "scipy-1.11.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7abda0e62ef00cde826d441485e2e32fe737bdddee3324e35c0e01dee65e2a88"}, - {file = "scipy-1.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929"}, - {file = "scipy-1.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165"}, - {file = "scipy-1.11.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a"}, - {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3"}, - {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221"}, - {file = "scipy-1.11.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:91770cb3b1e81ae19463b3c235bf1e0e330767dca9eb4cd73ba3ded6c4151e4d"}, - {file = "scipy-1.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820"}, - {file = "scipy-1.11.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15"}, - {file = "scipy-1.11.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7"}, - {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc"}, - {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6"}, - {file = "scipy-1.11.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:74e89dc5e00201e71dd94f5f382ab1c6a9f3ff806c7d24e4e90928bb1aafb280"}, - {file = "scipy-1.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6"}, - {file = "scipy-1.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83"}, - {file = "scipy-1.11.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e"}, - {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917"}, - {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156"}, - {file = "scipy-1.11.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15f237e890c24aef6891c7d008f9ff7e758c6ef39a2b5df264650eb7900403c0"}, - {file = "scipy-1.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2"}, - {file = "scipy-1.11.3.tar.gz", hash = "sha256:bba4d955f54edd61899776bad459bf7326e14b9fa1c552181f0479cc60a568cd"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, + {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, + {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, + {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, + {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, + {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, + {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, + {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, + {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, + {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, ] [package.dependencies] -numpy = ">=1.21.6,<1.28.0" +numpy = ">=1.22.4,<1.29.0" [package.extras] dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "setuptools" -version = "68.2.2" +version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] -[[package]] -name = "setuptools-scm" -version = "8.0.4" -description = "the blessed package to manage your versions by scm tags" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, - {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, -] - -[package.dependencies] -packaging = ">=20" -setuptools = "*" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} -typing-extensions = "*" - -[package.extras] -docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] -rich = ["rich"] -test = ["build", "pytest", "rich", "wheel"] - [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -3626,7 +3476,6 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3638,7 +3487,6 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -category = "dev" optional = false python-versions = "*" files = [ @@ -3648,70 +3496,70 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.21" +version = "2.0.25" description = "Database Abstraction Library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"}, - {file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"}, - {file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:56628ca27aa17b5890391ded4e385bf0480209726f198799b7e980c6bd473bd7"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db726be58837fe5ac39859e0fa40baafe54c6d54c02aba1d47d25536170b690f"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7421c1bfdbb7214313919472307be650bd45c4dc2fcb317d64d078993de045b"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:632784f7a6f12cfa0e84bf2a5003b07660addccf5563c132cd23b7cc1d7371a9"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f6f7276cf26145a888f2182a98f204541b519d9ea358a65d82095d9c9e22f917"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2a1f7ffac934bc0ea717fa1596f938483fb8c402233f9b26679b4f7b38d6ab6e"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-win32.whl", hash = "sha256:bfece2f7cec502ec5f759bbc09ce711445372deeac3628f6fa1c16b7fb45b682"}, - {file = "SQLAlchemy-2.0.21-cp312-cp312-win_amd64.whl", hash = "sha256:526b869a0f4f000d8d8ee3409d0becca30ae73f494cbb48801da0129601f72c6"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"}, - {file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"}, - {file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"}, - {file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"}, - {file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"}, - {file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4344d059265cc8b1b1be351bfb88749294b87a8b2bbe21dfbe066c4199541ebd"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9e2e59cbcc6ba1488404aad43de005d05ca56e069477b33ff74e91b6319735"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84daa0a2055df9ca0f148a64fdde12ac635e30edbca80e87df9b3aaf419e144a"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc8b7dabe8e67c4832891a5d322cec6d44ef02f432b4588390017f5cec186a84"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5693145220517b5f42393e07a6898acdfe820e136c98663b971906120549da5"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db854730a25db7c956423bb9fb4bdd1216c839a689bf9cc15fada0a7fb2f4570"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win32.whl", hash = "sha256:14a6f68e8fc96e5e8f5647ef6cda6250c780612a573d99e4d881581432ef1669"}, + {file = "SQLAlchemy-2.0.25-cp310-cp310-win_amd64.whl", hash = "sha256:87f6e732bccd7dcf1741c00f1ecf33797383128bd1c90144ac8adc02cbb98643"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:342d365988ba88ada8af320d43df4e0b13a694dbd75951f537b2d5e4cb5cd002"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f37c0caf14b9e9b9e8f6dbc81bc56db06acb4363eba5a633167781a48ef036ed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa9373708763ef46782d10e950b49d0235bfe58facebd76917d3f5cbf5971aed"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24f571990c05f6b36a396218f251f3e0dda916e0c687ef6fdca5072743208f5"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75432b5b14dc2fff43c50435e248b45c7cdadef73388e5610852b95280ffd0e9"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:884272dcd3ad97f47702965a0e902b540541890f468d24bd1d98bcfe41c3f018"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win32.whl", hash = "sha256:e607cdd99cbf9bb80391f54446b86e16eea6ad309361942bf88318bcd452363c"}, + {file = "SQLAlchemy-2.0.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d505815ac340568fd03f719446a589162d55c52f08abd77ba8964fbb7eb5b5f"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0dacf67aee53b16f365c589ce72e766efaabd2b145f9de7c917777b575e3659d"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b801154027107461ee992ff4b5c09aa7cc6ec91ddfe50d02bca344918c3265c6"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59a21853f5daeb50412d459cfb13cb82c089ad4c04ec208cd14dddd99fc23b39"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29049e2c299b5ace92cbed0c1610a7a236f3baf4c6b66eb9547c01179f638ec5"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b64b183d610b424a160b0d4d880995e935208fc043d0302dd29fee32d1ee3f95"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f7a7d7fcc675d3d85fbf3b3828ecd5990b8d61bd6de3f1b260080b3beccf215"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win32.whl", hash = "sha256:cf18ff7fc9941b8fc23437cc3e68ed4ebeff3599eec6ef5eebf305f3d2e9a7c2"}, + {file = "SQLAlchemy-2.0.25-cp312-cp312-win_amd64.whl", hash = "sha256:91f7d9d1c4dd1f4f6e092874c128c11165eafcf7c963128f79e28f8445de82d5"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bb209a73b8307f8fe4fe46f6ad5979649be01607f11af1eb94aa9e8a3aaf77f0"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798f717ae7c806d67145f6ae94dc7c342d3222d3b9a311a784f371a4333212c7"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fdd402169aa00df3142149940b3bf9ce7dde075928c1886d9a1df63d4b8de62"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0d3cab3076af2e4aa5693f89622bef7fa770c6fec967143e4da7508b3dceb9b9"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74b080c897563f81062b74e44f5a72fa44c2b373741a9ade701d5f789a10ba23"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win32.whl", hash = "sha256:87d91043ea0dc65ee583026cb18e1b458d8ec5fc0a93637126b5fc0bc3ea68c4"}, + {file = "SQLAlchemy-2.0.25-cp37-cp37m-win_amd64.whl", hash = "sha256:75f99202324383d613ddd1f7455ac908dca9c2dd729ec8584c9541dd41822a2c"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:420362338681eec03f53467804541a854617faed7272fe71a1bfdb07336a381e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c88f0c7dcc5f99bdb34b4fd9b69b93c89f893f454f40219fe923a3a2fd11625"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3be4987e3ee9d9a380b66393b77a4cd6d742480c951a1c56a23c335caca4ce3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a159111a0f58fb034c93eeba211b4141137ec4b0a6e75789ab7a3ef3c7e7e3"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8b8cb63d3ea63b29074dcd29da4dc6a97ad1349151f2d2949495418fd6e48db9"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:736ea78cd06de6c21ecba7416499e7236a22374561493b456a1f7ffbe3f6cdb4"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win32.whl", hash = "sha256:10331f129982a19df4284ceac6fe87353ca3ca6b4ca77ff7d697209ae0a5915e"}, + {file = "SQLAlchemy-2.0.25-cp38-cp38-win_amd64.whl", hash = "sha256:c55731c116806836a5d678a70c84cb13f2cedba920212ba7dcad53260997666d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:605b6b059f4b57b277f75ace81cc5bc6335efcbcc4ccb9066695e515dbdb3900"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:665f0a3954635b5b777a55111ababf44b4fc12b1f3ba0a435b602b6387ffd7cf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecf6d4cda1f9f6cb0b45803a01ea7f034e2f1aed9475e883410812d9f9e3cfcf"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c51db269513917394faec5e5c00d6f83829742ba62e2ac4fa5c98d58be91662f"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:790f533fa5c8901a62b6fef5811d48980adeb2f51f1290ade8b5e7ba990ba3de"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1b1180cda6df7af84fe72e4530f192231b1f29a7496951db4ff38dac1687202d"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win32.whl", hash = "sha256:555651adbb503ac7f4cb35834c5e4ae0819aab2cd24857a123370764dc7d7e24"}, + {file = "SQLAlchemy-2.0.25-cp39-cp39-win_amd64.whl", hash = "sha256:dc55990143cbd853a5d038c05e79284baedf3e299661389654551bd02a6a68d7"}, + {file = "SQLAlchemy-2.0.25-py3-none-any.whl", hash = "sha256:a86b4240e67d4753dc3092d9511886795b3c2852abe599cffe108952f7af7ac3"}, + {file = "SQLAlchemy-2.0.25.tar.gz", hash = "sha256:a2c69a7664fb2d54b8682dd774c3b54f67f84fa123cf84dda2a5f40dcaa04e08"}, ] [package.dependencies] greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.2.0" +typing-extensions = ">=4.6.0" [package.extras] aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] @@ -3721,7 +3569,7 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)"] mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] +oracle = ["cx_oracle (>=8)"] oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] @@ -3731,13 +3579,12 @@ postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "starlette" version = "0.27.0" description = "The little ASGI library that shines." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3755,7 +3602,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam name = "tensorboardx" version = "2.6.2.2" description = "TensorBoardX lets you watch Tensors Flow without Tensorflow" -category = "main" optional = false python-versions = "*" files = [ @@ -3772,7 +3618,6 @@ protobuf = ">=3.20" name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3782,63 +3627,58 @@ files = [ [[package]] name = "toolz" -version = "0.12.0" +version = "0.12.1" description = "List processing tools and functional utilities" -category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "toolz-0.12.0-py3-none-any.whl", hash = "sha256:2059bd4148deb1884bb0eb770a3cde70e7f954cfbbdc2285f1f2de01fd21eb6f"}, - {file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"}, + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, ] [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "tzdata" -version = "2023.3" +version = "2023.4" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, ] [[package]] name = "tzlocal" -version = "5.1" +version = "5.2" description = "tzinfo object for the local timezone" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tzlocal-5.1-py3-none-any.whl", hash = "sha256:2938498395d5f6a898ab8009555cb37a4d360913ad375d4747ef16826b03ef23"}, - {file = "tzlocal-5.1.tar.gz", hash = "sha256:a5ccb2365b295ed964e0a98ad076fe10c495591e75505d34f154d60a7f1ed722"}, + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, ] [package.dependencies] tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] -devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] [[package]] name = "urllib3" version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3856,7 +3696,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "uvicorn" version = "0.22.0" description = "The lightning-fast ASGI server." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3875,7 +3714,6 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", name = "validators" version = "0.20.0" description = "Python Data Validation for Humansâ„¢." -category = "main" optional = false python-versions = ">=3.4" files = [ @@ -3890,20 +3728,19 @@ test = ["flake8 (>=2.4.0)", "isort (>=4.2.2)", "pytest (>=2.2.3)"] [[package]] name = "virtualenv" -version = "20.24.5" +version = "20.25.0" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, - {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] @@ -3913,7 +3750,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "web3" version = "6.8.0" description = "web3.py" -category = "main" optional = false python-versions = ">=3.7.2" files = [ @@ -3947,166 +3783,182 @@ tester = ["eth-tester[py-evm] (==v0.9.1-b.1)", "py-geth (>=3.11.0)"] [[package]] name = "websockets" -version = "11.0.3" +version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, - {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, - {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, - {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, - {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, - {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, - {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, - {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, - {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, - {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, - {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, - {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, - {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, - {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, ] [[package]] name = "yarl" -version = "1.9.2" +version = "1.9.4" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, ] [package.dependencies] @@ -4116,4 +3968,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10, <3.13" -content-hash = "f191ea80c51a23de06e7032f3b734e3cbc2ab245890943affa2e3677a6ce3eb6" +content-hash = "ec6d9842af954ffe6ba6e3c67ef593182bb3deb283a0efa994a12b42607fd36f" diff --git a/packages/examples/cvat/recording-oracle/pyproject.toml b/packages/examples/cvat/recording-oracle/pyproject.toml index 60572fb11f..d3402bce81 100644 --- a/packages/examples/cvat/recording-oracle/pyproject.toml +++ b/packages/examples/cvat/recording-oracle/pyproject.toml @@ -15,7 +15,7 @@ SQLAlchemy = "^2.0.17" psycopg2 = "^2.9.6" APScheduler = "^3.10.1" pytest = "^7.4.0" -human-protocol-sdk = "^1.1.15" +human-protocol-sdk = "^1.1.19" alembic = "^1.11.1" httpx = "^0.24.1" numpy = "^1.25.2" diff --git a/packages/examples/cvat/recording-oracle/src/chain/escrow.py b/packages/examples/cvat/recording-oracle/src/chain/escrow.py index e3e5d942cf..416cc10bba 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/recording-oracle/src/chain/escrow.py @@ -1,15 +1,16 @@ import json from typing import List -from human_protocol_sdk.constants import Status +from human_protocol_sdk.constants import ChainId, Status from human_protocol_sdk.escrow import EscrowClient, EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient from src.chain.web3 import get_web3 +from src.utils.cloud_storage import parse_bucket_url def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: - escrow = EscrowUtils.get_escrow(chain_id, escrow_address.lower()) + escrow = EscrowUtils.get_escrow(ChainId(chain_id), escrow_address) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") @@ -42,7 +43,22 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - manifest_content = StorageClient.download_file_from_url(escrow.manifestUrl) + + parsed_url = parse_bucket_url(escrow.manifest_url) + + secure = False + if parsed_url.host_url.startswith("https://"): + host = parsed_url.host_url[len("https://") :] + secure = True + elif parsed_url.host_url.startswith("http://"): + host = parsed_url.host_url[len("http://") :] + else: + host = parsed_url.host_url + + manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( + [parsed_url.path], bucket=parsed_url.bucket_name + )[0] + return json.loads(manifest_content.decode("utf-8")) @@ -54,8 +70,8 @@ def store_results(chain_id: int, escrow_address: str, url: str, hash: str) -> No def get_reputation_oracle_address(chain_id: int, escrow_address: str) -> str: - return get_escrow(chain_id, escrow_address).reputationOracle + return get_escrow(chain_id, escrow_address).reputation_oracle def get_exchange_oracle_address(chain_id: int, escrow_address: str) -> str: - return get_escrow(chain_id, escrow_address).exchangeOracle + return get_escrow(chain_id, escrow_address).exchange_oracle diff --git a/packages/examples/cvat/recording-oracle/src/chain/kvstore.py b/packages/examples/cvat/recording-oracle/src/chain/kvstore.py index 50481bdd05..b6a436230f 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/kvstore.py +++ b/packages/examples/cvat/recording-oracle/src/chain/kvstore.py @@ -1,5 +1,6 @@ +from human_protocol_sdk.constants import ChainId from human_protocol_sdk.kvstore import KVStoreClient -from human_protocol_sdk.staking import StakingClient +from human_protocol_sdk.staking import StakingUtils from src.chain.escrow import get_escrow from src.chain.web3 import get_web3 @@ -21,9 +22,7 @@ def get_exchange_oracle_url(chain_id: int, escrow_address: str) -> str: escrow = get_escrow(chain_id, escrow_address) - web3 = get_web3(chain_id) - staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.exchangeOracle)["webhook_url"] + return StakingUtils.get_leader(ChainId(chain_id), escrow.exchange_oracle).webhook_url def get_reputation_oracle_url(chain_id: int, escrow_address: str) -> str: @@ -32,6 +31,4 @@ def get_reputation_oracle_url(chain_id: int, escrow_address: str) -> str: escrow = get_escrow(chain_id, escrow_address) - web3 = get_web3(chain_id) - staking_client = StakingClient(web3) - return staking_client.get_leader(escrow.reputationOracle)["webhook_url"] + return StakingUtils.get_leader(ChainId(chain_id), escrow.reputation_oracle).webhook_url From 7078629d967dfde783f9f01bf2b74f797bb82d12 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 30 Jan 2024 18:36:58 +0200 Subject: [PATCH 11/82] Fix escrow access --- packages/examples/cvat/exchange-oracle/src/chain/escrow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 55b6fb9eb5..ac8c8b2910 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -30,7 +30,7 @@ def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: manifest_url="http://127.0.0.1:9010/manifests/manifest_boxes_from_points_local.json", ) - escrow = EscrowUtils.get_escrow(chain_id, escrow_address.lower()) + escrow = EscrowUtils.get_escrow(ChainId(chain_id), escrow_address) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") From 33e7cb431f4978ad16b155b4709358d4c954fd8a Mon Sep 17 00:00:00 2001 From: maya Date: Sun, 4 Feb 2024 01:36:29 +0100 Subject: [PATCH 12/82] [Exchange/Recoring oracles] Add GCS support --- .../cvat/exchange-oracle/src/chain/escrow.py | 16 +-- .../cvat/exchange-oracle/src/core/config.py | 27 ++--- .../cvat/exchange-oracle/src/core/manifest.py | 10 +- .../src/crons/state_trackers.py | 3 +- .../exchange-oracle/src/cvat/api_calls.py | 39 ++++++- .../src/handlers/job_creation.py | 63 +++++------ .../src/handlers/job_export.py | 14 +-- .../src/services/cloud/__init__.py | 2 +- .../src/services/cloud/client.py | 21 +++- .../exchange-oracle/src/services/cloud/gcs.py | 80 ++++++++++++++ .../exchange-oracle/src/services/cloud/s3.py | 60 +++++------ .../src/services/cloud/types.py | 102 ++++++++++++++---- .../src/services/cloud/utils.py | 88 +++------------ .../cvat/recording-oracle/src/chain/escrow.py | 16 +-- .../cvat/recording-oracle/src/core/config.py | 43 ++++---- .../recording-oracle/src/core/manifest.py | 6 +- .../handlers/process_intermediate_results.py | 17 ++- .../src/handlers/validation.py | 18 ++-- .../src/services/cloud/__init__.py | 3 +- .../src/services/cloud/client.py | 21 +++- .../src/services/cloud/gcs.py | 80 ++++++++++++++ .../recording-oracle/src/services/cloud/s3.py | 59 ++++------ .../src/services/cloud/types.py | 102 ++++++++++++++---- .../src/services/cloud/utils.py | 88 +++------------ .../services/cloud/test_client_service.py | 28 ++--- 25 files changed, 593 insertions(+), 413 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index ac8c8b2910..3c41f41a8d 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -6,7 +6,7 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient -from src.services.cloud.utils import parse_bucket_url +from src.services.cloud.types import BucketAccessInfo def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: @@ -64,19 +64,19 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - parsed_url = parse_bucket_url(escrow.manifest_url) + bucket_info = BucketAccessInfo.parse_obj(escrow.manifest_url) secure = False - if parsed_url.host_url.startswith("https://"): - host = parsed_url.host_url[len("https://") :] + if bucket_info.host_url.startswith("https://"): + host = bucket_info.host_url[len("https://") :] secure = True - elif parsed_url.host_url.startswith("http://"): - host = parsed_url.host_url[len("http://") :] + elif bucket_info.host_url.startswith("http://"): + host = bucket_info.host_url[len("http://") :] else: - host = parsed_url.host_url + host = bucket_info.host_url manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( - [parsed_url.path], bucket=parsed_url.bucket_name + [bucket_info.path], bucket=bucket_info.bucket_name )[0] return json.loads(manifest_content.decode("utf-8")) diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index e0b600a2da..4db35f343e 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -3,6 +3,7 @@ import os from dotenv import load_dotenv +from attrs.converters import to_bool from src.utils.logging import parse_log_level from src.utils.net import is_ipv4 @@ -10,12 +11,6 @@ load_dotenv() -def str_to_bool(val: str) -> bool: - from distutils.util import strtobool - - return val is True or strtobool(val) - - class PostgresConfig: port = os.environ.get("PG_PORT", "5432") host = os.environ.get("PG_HOST", "0.0.0.0") @@ -100,26 +95,26 @@ class StorageConfig: secret_key = os.environ.get("STORAGE_SECRET_KEY", "") data_bucket_name = os.environ.get("STORAGE_DATA_BUCKET_NAME", "") results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") - secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + # TODO: GCS key file @classmethod - def provider_endpoint_url(cls): - scheme = "https://" if cls.secure else "http://" + def get_scheme(cls) -> str: + return "https://" if cls.secure else "http://" - return f"{scheme}{cls.endpoint_url}" + @classmethod + def provider_endpoint_url(cls): + return f"{cls.get_scheme()}{cls.endpoint_url}" @classmethod def bucket_url(cls): - scheme = "https://" if cls.secure else "http://" - if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" + return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" - + return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: - enable_custom_cloud_host = str_to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) + enable_custom_cloud_host = to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) "Allows using a custom host in manifest bucket urls" default_export_timeout = int(os.environ.get("DEFAULT_EXPORT_TIMEOUT", 60)) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 6d6c293b09..d4f8731624 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,5 +1,5 @@ from decimal import Decimal -from typing import Optional +from typing import Optional, Union, Dict from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -8,11 +8,11 @@ class DataInfo(BaseModel): - data_url: AnyUrl - "Bucket URL, s3 only, virtual-hosted-style access" + data_url: Union[AnyUrl, Dict] + "Bucket URL, AWS s3 | GCS, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html - points_url: Optional[AnyUrl] = None + points_url: Optional[Union[AnyUrl, Dict]] = None "A path to an archive with a set of points in COCO Keypoints format, " "which provides information about all objects on images" @@ -57,7 +57,7 @@ class ValidationInfo(BaseModel): val_size: int = Field(default=2, gt=0) "Validation frames per job" - gt_url: AnyUrl + gt_url: Union[AnyUrl, Dict] "URL to the archive with Ground Truth annotations, the format is COCO keypoints" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index b79ed8fbdb..e0e9314c38 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -283,8 +283,7 @@ def retrieve_annotations() -> None: secret_key=StorageConfig.secret_key, ) existing_storage_files = set( - f.key - for f in storage_client.list_files( + storage_client.list_files( StorageConfig.data_bucket_name, prefix=compose_results_bucket_filename( project.escrow_address, diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 9a6fd5dd21..3914d080fc 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -1,11 +1,14 @@ import io import logging import zipfile +import json + +from io import BytesIO from datetime import timedelta from enum import Enum from http import HTTPStatus from time import sleep -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Dict, Any from cvat_sdk.api_client import ApiClient, Configuration, exceptions, models from cvat_sdk.api_client.api_client import Endpoint @@ -102,21 +105,49 @@ def get_api_client() -> ApiClient: def create_cloudstorage( - provider: str, bucket_host: str, bucket_name: str + provider: str, + bucket_name: str, + *, + credentials: Optional[Dict[str, Any]] = None, + bucket_host: Optional[str] = None, ) -> models.CloudStorageRead: + # credentials: access_key | secret_key | service_account_key + # CVAT credentials: key | secret_key | key_file + def to_cvat_credentials() -> Dict: + credentials_ = dict() + for cvat_field, field in zip(('key', 'secret_key', 'key_file'), ('access_key', 'secret_key', 'service_account_key')): + if (value := credentials.get(field)): + if cvat_field == 'key_file': + key_file = BytesIO(json.dumps(value).encode('utf-8')) + key_file.name = 'key_file.json' + key_file.seek(0) + credentials_[cvat_field] = key_file + else: + credentials_[cvat_field] = value + return credentials_ + + converted_credentials = to_cvat_credentials() + if converted_credentials: + credentials_type = models.CredentialsTypeEnum("KEY_SECRET_KEY_PAIR") if provider == "AWS_S3_BUCKET" \ + else models.CredentialsTypeEnum("KEY_FILE_PATH") + else: + credentials_type = models.CredentialsTypeEnum("ANONYMOUS_ACCESS") logger = logging.getLogger("app") + with get_api_client() as api_client: cloud_storage_write_request = models.CloudStorageWriteRequest( provider_type=models.ProviderTypeEnum(provider), resource=bucket_name, display_name=bucket_name, - credentials_type=models.CredentialsTypeEnum("ANONYMOUS_ACCESS"), + credentials_type=credentials_type, description=bucket_name, - specific_attributes=f"endpoint_url={bucket_host}", + **({"specific_attributes": f"endpoint_url={bucket_host}"} if bucket_host else {}), + **(converted_credentials if converted_credentials else {}), ) # CloudStorageWriteRequest try: (data, response) = api_client.cloudstorages_api.create( cloud_storage_write_request, + _content_type="multipart/form-data", ) return data diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index f6bfa99e62..1660e748af 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -27,8 +27,8 @@ from src.core.types import CvatLabelType, TaskStatus, TaskType from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME -from src.services.cloud import CloudProviders, StorageClient -from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url +from src.services.cloud import CloudProvider, StorageClient +from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger @@ -57,8 +57,8 @@ } CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { - CloudProviders.aws: "AWS_S3_BUCKET", - CloudProviders.gcs: "GOOGLE_CLOUD_STORAGE", + CloudProvider.aws: "AWS_S3_BUCKET", + CloudProvider.gcs: "GOOGLE_CLOUD_STORAGE", } @@ -151,7 +151,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.max_embedded_point_radius_percent = 0.01 self.embedded_point_color = (0, 255, 255) - self.oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config.bucket_url()) # TODO: add # credentials=BucketCredentials() "Exchange Oracle's private bucket info" @@ -179,29 +179,20 @@ def set_logger(self, logger: Logger): return self def _download_input_data(self): - data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) - gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) - points_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.points_url) + data_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) + gt_bucket = BucketAccessInfo.parse_obj(self.manifest.validation.gt_url) + points_bucket = BucketAccessInfo.parse_obj(self.manifest.data.points_url) data_storage_client = self._make_cloud_storage_client(data_bucket) gt_storage_client = self._make_cloud_storage_client(gt_bucket) points_storage_client = self._make_cloud_storage_client(points_bucket) - data_filenames = data_storage_client.list_filenames( - data_bucket.url.bucket_name, - prefix=data_bucket.url.path, - ) + data_filenames = data_storage_client.list_files(prefix=data_bucket.path) self.input_filenames = filter_image_files(data_filenames) - self.input_gt_data = gt_storage_client.download_file( - gt_bucket.url.bucket_name, - gt_bucket.url.path, - ) + self.input_gt_data = gt_storage_client.download_fileobj(gt_bucket.path) - self.input_points_data = points_storage_client.download_file( - points_bucket.url.bucket_name, - points_bucket.url.path, - ) + self.input_points_data = points_storage_client.download_fileobj(points_bucket.path) def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) @@ -721,7 +712,7 @@ def _upload_task_meta(self): ) storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) - bucket_name = self.oracle_data_bucket.url.bucket_name + bucket_name = self.oracle_data_bucket.bucket_name for file_data, filename in file_list: storage_client.create_file( bucket_name, @@ -769,7 +760,7 @@ def _extract_and_upload_rois(self): assert self.input_filenames is not self._not_configured assert self.roi_filenames is not self._not_configured - src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + src_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) src_prefix = "" dst_bucket = self.oracle_data_bucket @@ -793,9 +784,7 @@ def _extract_and_upload_rois(self): if not image_roi_infos: continue - image_bytes = src_client.download_file( - src_bucket.url.bucket_name, os.path.join(src_prefix, filename) - ) + image_bytes = src_client.download_fileobj(os.path.join(src_prefix, filename)) image_pixels = decode_image(image_bytes) sample = filename_to_sample[filename] @@ -824,7 +813,6 @@ def _extract_and_upload_rois(self): for roi_filename, roi_bytes in image_rois.items(): dst_client.create_file( - dst_bucket.url.bucket_name, compose_data_bucket_filename(self.escrow_address, self.chain_id, roi_filename), roi_bytes, ) @@ -833,20 +821,19 @@ def _create_on_cvat(self): assert self.job_layout is not self._not_configured assert self.label_configuration is not self._not_configured - input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + input_data_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) oracle_bucket = self.oracle_data_bucket # Register cloud storage on CVAT to pass user dataset cloud_storage = cvat_api.create_cloudstorage( CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[oracle_bucket.provider], - oracle_bucket.url.host_url.replace( + oracle_bucket.bucket_name, + bucket_host=oracle_bucket.host_url.replace( # TODO: remove mock "127.0.0.1", "172.22.0.1", ), - oracle_bucket.url.bucket_name, - # TODO: add - # credentials=... + **({ "credentials": oracle_bucket.credentials.to_dict() } if oracle_bucket.credentials else {}) ) # Create a project @@ -859,7 +846,6 @@ def _create_on_cvat(self): # Setup webhooks for a project (update:task, update:job) webhook = cvat_api.create_cvat_webhook(project.id) - input_data_bucket = parse_bucket_url(self.manifest.data.data_url) with SessionLocal.begin() as session: db_service.create_project( session, @@ -1019,21 +1005,19 @@ def create_task(escrow_address: str, chain_id: int) -> None: TaskType.image_points, TaskType.image_label_binary, ]: - data_bucket = parse_bucket_url(manifest.data.data_url) - gt_bucket = parse_bucket_url(manifest.validation.gt_url) + data_bucket = BucketAccessInfo.parse_obj(manifest.data.data_url) + gt_bucket = BucketAccessInfo.parse_obj(manifest.validation.gt_url) data_bucket_client = cloud_service.make_client(data_bucket) gt_bucket_client = cloud_service.make_client(gt_bucket) # Task configuration creation - data_filenames = data_bucket_client.list_filenames( - data_bucket.bucket_name, + data_filenames = data_bucket_client.list_files( prefix=data_bucket.path, ) data_filenames = filter_image_files(data_filenames) - gt_file_data = gt_bucket_client.download_file( - gt_bucket.bucket_name, + gt_file_data = gt_bucket_client.download_fileobj( gt_bucket.path, ) @@ -1046,8 +1030,9 @@ def create_task(escrow_address: str, chain_id: int) -> None: # Register cloud storage on CVAT to pass user dataset cloud_storage = cvat_api.create_cloudstorage( CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[data_bucket.provider], - data_bucket.host_url, data_bucket.bucket_name, + bucket_host=data_bucket.host_url, + **({ "credentials": data_bucket.credentials.to_dict() } if data_bucket.credentials else {}) ) # Create a project diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index ab0f87ead7..dc956abf75 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -186,7 +186,7 @@ def _download_task_meta(self): layout = boxes_from_points_task.TaskMetaLayout() serializer = boxes_from_points_task.TaskMetaSerializer() - oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config.bucket_url()) # TODO: add # credentials=BucketCredentials() "Exchange Oracle's private bucket info" @@ -194,8 +194,8 @@ def _download_task_meta(self): storage_client = make_cloud_client(oracle_data_bucket) roi_filenames = serializer.parse_roi_filenames( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( + oracle_data_bucket.bucket_name, compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME ), @@ -203,8 +203,8 @@ def _download_task_meta(self): ) rois = serializer.parse_roi_info( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( + oracle_data_bucket.bucket_name, compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME ), @@ -212,8 +212,8 @@ def _download_task_meta(self): ) points_dataset = serializer.parse_points_annotations( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( + oracle_data_bucket.bucket_name, compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.POINTS_FILENAME ), diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py index 007394d9ed..b04cfab903 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py @@ -1,3 +1,3 @@ from src.services.cloud.client import StorageClient -from src.services.cloud.types import CloudProviders +from src.services.cloud.types import CloudProvider from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index 0a80d1706e..c2d2b5cd3c 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -1,24 +1,35 @@ from abc import ABCMeta, abstractmethod from typing import List, Optional +from urllib.parse import unquote class StorageClient(metaclass=ABCMeta): + def __init__( + self, + bucket: Optional[str] = None, + ) -> None: + self._bucket = unquote(bucket) if bucket else None + @abstractmethod - def create_file(self, bucket: str, filename: str, data: bytes = b""): + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): ... @abstractmethod - def remove_file(self, bucket: str, filename: str): + def remove_file(self, key: str, *, bucket: Optional[str] = None): ... @abstractmethod - def file_exists(self, bucket: str, filename: str) -> bool: + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: ... @abstractmethod - def download_file(self, bucket: str, key: str) -> bytes: + def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: ... @abstractmethod - def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + def list_files(self, path: Optional[str] = None, *, bucket: Optional[str] = None) -> List[str]: ... + + @staticmethod + def normalize_prefix(prefix: Optional[str]) -> Optional[str]: + return unquote(prefix).strip("/\\") + "/" if prefix else prefix diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py new file mode 100644 index 0000000000..cb594504fa --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py @@ -0,0 +1,80 @@ +# Copyright (C) 2024 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import json +import os + +# from google.cloud.exceptions import Forbidden as GoogleCloudForbidden +# from google.cloud.exceptions import NotFound as GoogleCloudNotFound +from io import BytesIO +from typing import List, Optional, Union, Dict +from urllib.parse import unquote + +from google.cloud import storage + +from src.services.cloud.client import StorageClient + +# TODO: handle cases when bucket/file does not exist + +class GCSClient(StorageClient): + def __init__( + self, + *, + bucket: Optional[str] = None, + service_account_json: Optional[Union[str, Dict]] = None, + ) -> None: + super().__init__(bucket) + if service_account_json: + if isinstance(service_account_json, str): + service_account_json = json.loads(service_account_json) + self.client = storage.Client.from_service_account_info(service_account_json) + else: + self.client = storage.Client.create_anonymous_client() + + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.blob(unquote(key)).upload_from_string(data) + + def remove_file(self, key: str, *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.delete_blob(unquote(key)) + + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + return bucket_client.blob(unquote(key)).exists() + + def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + blob = bucket_client.blob(unquote(key)) + + with BytesIO() as data: + self.client.download_blob_to_file(blob, data) + return data.getvalue() + + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + # TODO: performance? + prefix = self.normalize_prefix(prefix) + + return [ + blob.name + for blob in self.client.list_blobs( + bucket_or_name=bucket, + fields="items(name)", + **( + { + "prefix": prefix, + "delimiter": "/", + } + if prefix + else {} + ), + ) + ] diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py index ed00f2799e..bc141b81ce 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT -from dataclasses import dataclass from io import BytesIO from typing import List, Optional from urllib.parse import unquote @@ -12,7 +11,8 @@ from botocore.handlers import disable_signing from src.services.cloud.client import StorageClient -from src.services.cloud.types import BucketCredentials + +DEFAULT_S3_HOST = "s3.amazonaws.com" class S3Client(StorageClient): @@ -20,9 +20,11 @@ def __init__( self, endpoint_url: str, *, + bucket: Optional[str] = None, access_key: Optional[str] = None, secret_key: Optional[str] = None, ) -> None: + super().__init__(bucket) s3 = boto3.resource( "s3", **(dict(aws_access_key_id=access_key) if access_key else {}), @@ -36,15 +38,18 @@ def __init__( if not access_key and not secret_key: self.client.meta.events.register("choose-signer.s3.*", disable_signing) - def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.put_object(Body=data, Bucket=bucket, Key=unquote(key)) - def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + def remove_file(self, key: str, *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.delete_object(Bucket=bucket, Key=unquote(key)) - def file_exists(self, bucket: str, filename: str) -> bool: + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket try: - self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) + self.client.head_object(Bucket=bucket, Key=unquote(key)) return True except ClientError as e: if e.response["Error"]["Code"] == "404": @@ -52,37 +57,20 @@ def file_exists(self, bucket: str, filename: str) -> bool: else: raise - def download_file(self, bucket: str, key: str) -> bytes: + def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket with BytesIO() as data: - self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) + self.client.download_fileobj(Bucket=bucket, Key=unquote(key), Fileobj=data) return data.getvalue() - def list_files(self, bucket: str, *, prefix: Optional[str] = None) -> List: - objects = self.resource.Bucket(unquote(bucket)).objects + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + # TODO: performance? + objects = self.resource.Bucket(bucket).objects if prefix: - objects = objects.filter(Prefix=unquote(prefix).strip("/\\") + "/") + objects = objects.filter(Prefix=self.normalize_prefix(prefix)) else: objects = objects.all() - return list(objects) - - def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: - return [file_info.key for file_info in self.list_files(bucket=bucket, prefix=prefix)] - - -@dataclass -class S3BucketCredentials(BucketCredentials): - access_key: str - secret_key: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_file(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, *, prefix: Optional[str] = None) -> List[str]: - client = S3Client(bucket_host) - return client.list_filenames(bucket_name, prefix=prefix) + return [file_info.key for file_info in objects] diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py index ee89883d52..785299b997 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -2,43 +2,109 @@ from dataclasses import dataclass from enum import Enum, auto -from typing import Optional +from typing import Dict, Optional, Union +from urllib.parse import urlparse +from src.core.config import Config +from src.services.cloud.s3 import DEFAULT_S3_HOST from src.utils.enums import BetterEnumMeta +from src.utils.net import is_ipv4 -class CloudProviders(Enum, metaclass=BetterEnumMeta): +class CloudProvider(Enum, metaclass=BetterEnumMeta): aws = auto() gcs = auto() +class BucketCredentials: + def to_dict(self) -> Dict: + return self.__dict__ + + @dataclass -class BucketUrl: - provider: CloudProviders - host_url: str - bucket_name: str - path: str +class GCSCredentials(BucketCredentials): + service_account_key: Dict -class BucketCredentials: - pass +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str @dataclass class BucketAccessInfo: - url: BucketUrl + provider: CloudProvider + host_url: str + bucket_name: str + path: Optional[str] = None credentials: Optional[BucketCredentials] = None @classmethod - def from_raw_url(cls, url: str) -> BucketAccessInfo: - from src.services.cloud.utils import parse_bucket_url + def from_url(cls, data: str) -> BucketAccessInfo: + parsed_url = urlparse(data) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketAccessInfo( + provider=CloudProvider.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + elif parsed_url.netloc.endswith("storage.googleapis.com"): + # TODO + # Google Cloud Storage (GCS) bucket + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + return BucketAccessInfo( + provider=CloudProvider.gcs, + bucket_name=bucket_name, + host_url=f"{parsed_url.scheme}://{parsed_url.netloc}", + path=path, + ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketAccessInfo( + provider=CloudProvider.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + + @classmethod + def from_dict(cls, data: Dict) -> BucketAccessInfo: + for required_field in ( + "provider", + "bucket_name", + ): # probably host_url too + if required_field not in data: + assert False, f"Missed {required_field} param in bucket configuration" + + data['provider'] = { + 'aws': CloudProvider.aws, + 'gcs': CloudProvider.gcs, + }[data['provider'].lower()] + + if (access_key := data.pop("access_key", None)) and (secret_key := data.pop("secret_key", None)): + data["credentials"] = S3BucketCredentials(access_key, secret_key) + + elif service_account_key := data.pop("service_account_key", None): + data["credentials"] = GCSCredentials(service_account_key) - return cls.from_parsed_url(parse_bucket_url(url)) + return BucketAccessInfo(**data) @classmethod - def from_parsed_url(cls, parsed_url: BucketUrl) -> BucketAccessInfo: - return BucketAccessInfo(url=parsed_url) + def parse_obj(cls, data: Union[Dict, str]) -> BucketAccessInfo: + if isinstance(data, Dict): + return cls.from_dict(data) - @property - def provider(self) -> CloudProviders: - return self.url.provider + return cls.from_url(data) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py index 33b9a4f7ee..ec31417643 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py @@ -1,95 +1,41 @@ -from typing import Optional, Union, overload -from urllib.parse import urlparse +from typing import Optional -from src.core.config import Config from src.services.cloud.client import StorageClient -from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client -from src.services.cloud.types import BucketAccessInfo, BucketCredentials, BucketUrl, CloudProviders -from src.utils.net import is_ipv4 - - -def parse_bucket_url(data_url: str) -> BucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return BucketUrl( - provider=CloudProviders.aws, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return BucketUrl( - provider=CloudProviders.aws, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") +from src.services.cloud.gcs import GCSClient +from src.services.cloud.s3 import S3Client +from src.services.cloud.types import BucketAccessInfo, CloudProvider def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None + bucket_name: str, provider: CloudProvider, *, bucket_host: Optional[str] = None ) -> str: match provider: - case CloudProviders.aws: + case CloudProvider.aws: return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs: + case CloudProvider.gcs: return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" -@overload -def make_client(url: BucketUrl, credentials: Optional[BucketCredentials] = None) -> StorageClient: - ... - - -@overload def make_client( bucket_info: BucketAccessInfo, ) -> StorageClient: - ... - - -def make_client( - _pos1: Union[BucketUrl, BucketAccessInfo, None] = None, - *, - bucket_info: Optional[BucketAccessInfo] = None, - url: Optional[BucketUrl] = None, - credentials: Optional[BucketCredentials] = None, -) -> StorageClient: - if _pos1 is not None: - if isinstance(_pos1, BucketAccessInfo): - bucket_info = _pos1 - else: - url = _pos1 - - if bucket_info is None: - bucket_info = BucketAccessInfo(url=url, credentials=credentials) + client_kwargs = { + "bucket": bucket_info.bucket_name, + } match bucket_info.provider: - case CloudProviders.aws: + case CloudProvider.aws: client_kwargs = {} if bucket_info.credentials: client_kwargs["access_key"] = bucket_info.credentials.access_key client_kwargs["secret_key"] = bucket_info.credentials.secret_key - client = S3Client(bucket_info.url.host_url, **client_kwargs) + client = S3Client(bucket_info.host_url, **client_kwargs) + case CloudProvider.gcs: + if bucket_info.credentials: + client_kwargs["service_account_json"] = bucket_info.credentials.service_account_key + + client = GCSClient(**client_kwargs) case _: raise Exception("Unsupported cloud provider") diff --git a/packages/examples/cvat/recording-oracle/src/chain/escrow.py b/packages/examples/cvat/recording-oracle/src/chain/escrow.py index 7c325e1a74..e4e8c315de 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/recording-oracle/src/chain/escrow.py @@ -7,7 +7,7 @@ from human_protocol_sdk.storage import StorageClient from src.chain.web3 import get_web3 -from src.services.cloud.utils import parse_bucket_url +from src.services.cloud.types import BucketAccessInfo def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: @@ -65,19 +65,19 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - parsed_url = parse_bucket_url(escrow.manifest_url) + bucket_access_info = BucketAccessInfo.parse_obj(escrow.manifest_url) secure = False - if parsed_url.host_url.startswith("https://"): - host = parsed_url.host_url[len("https://") :] + if bucket_access_info.host_url.startswith("https://"): + host = bucket_access_info.host_url[len("https://") :] secure = True - elif parsed_url.host_url.startswith("http://"): - host = parsed_url.host_url[len("http://") :] + elif bucket_access_info.host_url.startswith("http://"): + host = bucket_access_info.host_url[len("http://") :] else: - host = parsed_url.host_url + host = bucket_access_info.host_url manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( - [parsed_url.path], bucket=parsed_url.bucket_name + [bucket_access_info.path], bucket=bucket_access_info.bucket_name )[0] return json.loads(manifest_content.decode("utf-8")) diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index a647973007..54f9c37aa5 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -3,6 +3,7 @@ import os from dotenv import load_dotenv +from attrs.converters import to_bool from src.utils.logging import parse_log_level from src.utils.net import is_ipv4 @@ -10,12 +11,6 @@ load_dotenv() -def str_to_bool(val: str) -> bool: - from distutils.util import strtobool - - return val is True or strtobool(val) - - class Postgres: port = os.environ.get("PG_PORT", "5434") host = os.environ.get("PG_HOST", "0.0.0.0") @@ -77,22 +72,23 @@ class StorageConfig: access_key = os.environ.get("STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("STORAGE_SECRET_KEY", "") data_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") - secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + # TODO: GCS key file @classmethod - def provider_endpoint_url(cls): - scheme = "https://" if cls.secure else "http://" + def get_scheme(cls) -> str: + return "https://" if cls.secure else "http://" - return f"{scheme}{cls.endpoint_url}" + @classmethod + def provider_endpoint_url(cls): + return f"{cls.get_scheme()}{cls.endpoint_url}" @classmethod def bucket_url(cls): - scheme = "https://" if cls.secure else "http://" - if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" + return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" + return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" class ExchangeOracleStorageConfig: @@ -102,26 +98,27 @@ class ExchangeOracleStorageConfig: secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY", "") data_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") - secure = str_to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) + secure = to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) + # TODO: GCS key file @classmethod - def provider_endpoint_url(cls): - scheme = "https://" if cls.secure else "http://" + def get_scheme(cls) -> str: + return "https://" if cls.secure else "http://" - return f"{scheme}{cls.endpoint_url}" + @classmethod + def provider_endpoint_url(cls): + return f"{cls.get_scheme()}{cls.endpoint_url}" @classmethod def bucket_url(cls): - scheme = "https://" if cls.secure else "http://" - if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" + return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" + return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: - enable_custom_cloud_host = str_to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) + enable_custom_cloud_host = to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) "Allows using a custom host in manifest bucket urls" default_point_validity_relative_radius = float( diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 00bd265b82..00276e8594 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -1,5 +1,5 @@ from decimal import Decimal -from typing import Optional +from typing import Optional, Union, Dict from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -8,7 +8,7 @@ class DataInfo(BaseModel): - data_url: AnyUrl + data_url: Union[AnyUrl, Dict] "Bucket URL, s3 only, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html @@ -53,7 +53,7 @@ class ValidationInfo(BaseModel): val_size: int = Field(default=2, gt=0) "Validation frames per job" - gt_url: AnyUrl + gt_url: Union[AnyUrl, Dict] "URL to the archive with Ground Truth annotations, the format is COCO keypoints" diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 5d786b0a28..a536bb8f3a 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -199,7 +199,7 @@ def _download_task_meta(self): layout = boxes_from_points_task.TaskMetaLayout() serializer = boxes_from_points_task.TaskMetaSerializer() - oracle_data_bucket = BucketAccessInfo.from_raw_url( + oracle_data_bucket = BucketAccessInfo.parse_obj( Config.exchange_oracle_storage_config.bucket_url() ) # TODO: add @@ -209,8 +209,7 @@ def _download_task_meta(self): storage_client = make_cloud_client(oracle_data_bucket) boxes_to_points_mapping = serializer.parse_bbox_point_mapping( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.BBOX_POINT_MAPPING_FILENAME ), @@ -218,8 +217,7 @@ def _download_task_meta(self): ) roi_filenames = serializer.parse_roi_filenames( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME ), @@ -227,8 +225,7 @@ def _download_task_meta(self): ) rois = serializer.parse_roi_info( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME ), @@ -236,8 +233,7 @@ def _download_task_meta(self): ) gt_dataset = serializer.parse_gt_annotations( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.GT_FILENAME ), @@ -245,8 +241,7 @@ def _download_task_meta(self): ) points_dataset = serializer.parse_points_annotations( - storage_client.download_file( - oracle_data_bucket.url.bucket_name, + storage_client.download_fileobj( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.POINTS_FILENAME ), diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index ec9030f8ff..6bedbd347d 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -46,7 +46,7 @@ def __init__( self.db_session = db_session self.logger: Logger = NullLogger() - self.data_bucket = BucketAccessInfo.from_raw_url( + self.data_bucket = BucketAccessInfo.parse_obj( Config.exchange_oracle_storage_config.bucket_url() ) @@ -66,8 +66,8 @@ def _download_results_meta(self): self.chain_id, annotation.ANNOTATION_RESULTS_METAFILE_NAME, ) - annotation_metafile_data = data_bucket_client.download_file( - self.data_bucket.url.bucket_name, annotation_meta_path + annotation_metafile_data = data_bucket_client.download_fileobj( + annotation_meta_path ) self.annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) @@ -83,26 +83,22 @@ def _download_annotations(self): self.chain_id, job_meta.annotation_filename, ) - job_annotations[job_meta.job_id] = data_bucket_client.download_file( - self.data_bucket.url.bucket_name, job_filename - ) + job_annotations[job_meta.job_id] = data_bucket_client.download_fileobj(job_filename) excor_merged_annotation_path = compose_annotation_results_bucket_filename( self.escrow_address, self.chain_id, annotation.RESULTING_ANNOTATIONS_FILE, ) - merged_annotations = data_bucket_client.download_file( - self.data_bucket.url.bucket_name, excor_merged_annotation_path - ) + merged_annotations = data_bucket_client.download_fileobj(excor_merged_annotation_path) self.job_annotations = job_annotations self.merged_annotations = merged_annotations def _download_gt(self): - gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + gt_bucket = BucketAccessInfo.parse_obj(self.manifest.validation.gt_url) gt_bucket_client = make_cloud_client(gt_bucket) - self.gt_data = gt_bucket_client.download_file(gt_bucket.url.bucket_name, gt_bucket.url.path) + self.gt_data = gt_bucket_client.download_fileobj(gt_bucket.path) def _download_results(self): self._download_results_meta() diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py index 007394d9ed..45a193e20b 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py @@ -1,3 +1,4 @@ from src.services.cloud.client import StorageClient -from src.services.cloud.types import CloudProviders +from src.services.cloud.types import CloudProvider from src.services.cloud.utils import make_client +from src.services.cloud.s3 import S3Client diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py index 0a80d1706e..c2d2b5cd3c 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py @@ -1,24 +1,35 @@ from abc import ABCMeta, abstractmethod from typing import List, Optional +from urllib.parse import unquote class StorageClient(metaclass=ABCMeta): + def __init__( + self, + bucket: Optional[str] = None, + ) -> None: + self._bucket = unquote(bucket) if bucket else None + @abstractmethod - def create_file(self, bucket: str, filename: str, data: bytes = b""): + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): ... @abstractmethod - def remove_file(self, bucket: str, filename: str): + def remove_file(self, key: str, *, bucket: Optional[str] = None): ... @abstractmethod - def file_exists(self, bucket: str, filename: str) -> bool: + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: ... @abstractmethod - def download_file(self, bucket: str, key: str) -> bytes: + def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: ... @abstractmethod - def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + def list_files(self, path: Optional[str] = None, *, bucket: Optional[str] = None) -> List[str]: ... + + @staticmethod + def normalize_prefix(prefix: Optional[str]) -> Optional[str]: + return unquote(prefix).strip("/\\") + "/" if prefix else prefix diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py new file mode 100644 index 0000000000..17db6a9346 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py @@ -0,0 +1,80 @@ +# Copyright (C) 2024 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +import json +import os + +# from google.cloud.exceptions import Forbidden as GoogleCloudForbidden +# from google.cloud.exceptions import NotFound as GoogleCloudNotFound +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +from google.cloud import storage + +from src.services.cloud.client import StorageClient + +# TODO: handle cases when bucket/file does not exist + +class GCSClient(StorageClient): + def __init__( + self, + *, + bucket: Optional[str] = None, + service_account_json: Optional[str] = None, + ) -> None: + super().__init__(bucket) + if service_account_json: + if isinstance(service_account_json, str): + service_account_json = json.loads(service_account_json) + self.client = storage.Client.from_service_account_info(service_account_json) + else: + self.client = storage.Client.create_anonymous_client() + + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.blob(unquote(key)).upload_from_string(data) + + def remove_file(self, key: str, *, bucket: Optional[str] = None) -> None: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + bucket_client.delete_blob(unquote(key)) + + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + return bucket_client.blob(unquote(key)).exists() + + def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket + bucket_client = self.client.get_bucket(bucket) + blob = bucket_client.blob(unquote(key)) + + with BytesIO() as data: + self.client.download_blob_to_file(blob, data) + return data.getvalue() + + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + # TODO: performance? + prefix = self.normalize_prefix(prefix) + + return [ + blob.name + for blob in self.client.list_blobs( + bucket_or_name=bucket, + fields="items(name)", + **( + { + "prefix": prefix, + "delimiter": "/", + } + if prefix + else {} + ), + ) + ] diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py index ed00f2799e..b3510a3cf1 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT -from dataclasses import dataclass from io import BytesIO from typing import List, Optional from urllib.parse import unquote @@ -12,7 +11,8 @@ from botocore.handlers import disable_signing from src.services.cloud.client import StorageClient -from src.services.cloud.types import BucketCredentials + +DEFAULT_S3_HOST = "s3.amazonaws.com" class S3Client(StorageClient): @@ -20,9 +20,11 @@ def __init__( self, endpoint_url: str, *, + bucket: Optional[str] = None, access_key: Optional[str] = None, secret_key: Optional[str] = None, ) -> None: + super().__init__(bucket) s3 = boto3.resource( "s3", **(dict(aws_access_key_id=access_key) if access_key else {}), @@ -36,15 +38,18 @@ def __init__( if not access_key and not secret_key: self.client.meta.events.register("choose-signer.s3.*", disable_signing) - def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + def create_file(self, key: str, data: bytes = b"", *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.put_object(Body=data, Bucket=bucket, Key=unquote(key)) - def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + def remove_file(self, key: str, *, bucket: Optional[str] = None): + bucket = unquote(bucket) if bucket else self._bucket + self.client.delete_object(Bucket=bucket, Key=unquote(key)) - def file_exists(self, bucket: str, filename: str) -> bool: + def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: + bucket = unquote(bucket) if bucket else self._bucket try: - self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) + self.client.head_object(Bucket=bucket, Key=unquote(key)) return True except ClientError as e: if e.response["Error"]["Code"] == "404": @@ -52,37 +57,19 @@ def file_exists(self, bucket: str, filename: str) -> bool: else: raise - def download_file(self, bucket: str, key: str) -> bytes: + def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + bucket = unquote(bucket) if bucket else self._bucket with BytesIO() as data: - self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) + self.client.download_fileobj(Bucket=bucket, Key=unquote(key), Fileobj=data) return data.getvalue() - def list_files(self, bucket: str, *, prefix: Optional[str] = None) -> List: - objects = self.resource.Bucket(unquote(bucket)).objects + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: + bucket = unquote(bucket) if bucket else self._bucket + objects = self.resource.Bucket(bucket).objects if prefix: - objects = objects.filter(Prefix=unquote(prefix).strip("/\\") + "/") + objects = objects.filter(Prefix=self.normalize_prefix(prefix)) else: objects = objects.all() - return list(objects) - - def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: - return [file_info.key for file_info in self.list_files(bucket=bucket, prefix=prefix)] - - -@dataclass -class S3BucketCredentials(BucketCredentials): - access_key: str - secret_key: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_file(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, *, prefix: Optional[str] = None) -> List[str]: - client = S3Client(bucket_host) - return client.list_filenames(bucket_name, prefix=prefix) + return [file_info.key for file_info in objects] diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py index ee89883d52..b70d561aad 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -2,43 +2,109 @@ from dataclasses import dataclass from enum import Enum, auto -from typing import Optional +from typing import Dict, Optional, Union +from urllib.parse import urlparse +from src.core.config import Config +from src.services.cloud.s3 import DEFAULT_S3_HOST from src.utils.enums import BetterEnumMeta +from src.utils.net import is_ipv4 -class CloudProviders(Enum, metaclass=BetterEnumMeta): +class CloudProvider(Enum, metaclass=BetterEnumMeta): aws = auto() gcs = auto() +class BucketCredentials: + def to_dict(self) -> Dict: + return self.__dict__ + @dataclass -class BucketUrl: - provider: CloudProviders - host_url: str - bucket_name: str - path: str +class GCSCredentials(BucketCredentials): + service_account_key: Dict -class BucketCredentials: - pass +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str @dataclass class BucketAccessInfo: - url: BucketUrl + provider: CloudProvider + host_url: str + bucket_name: str + path: Optional[str] = None credentials: Optional[BucketCredentials] = None @classmethod - def from_raw_url(cls, url: str) -> BucketAccessInfo: - from src.services.cloud.utils import parse_bucket_url + def from_url(cls, data: str) -> BucketAccessInfo: + parsed_url = urlparse(data) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketAccessInfo( + provider=CloudProvider.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + # TODO: + elif parsed_url.netloc.endswith("storage.googleapis.com"): + # https://testintegrationwithgcs.storage.googleapis.com + # Google Cloud Storage (GCS) bucket + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + return BucketAccessInfo( + provider=CloudProvider.gcs, + bucket_name=bucket_name, + host_url=f"{parsed_url.scheme}://{parsed_url.netloc}", + path=path, + ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketAccessInfo( + provider=CloudProvider.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + + @classmethod + def from_dict(cls, data: Dict) -> BucketAccessInfo: + for required_field in ( + "provider", + "bucket_name", + ): # probably host_url too + if required_field not in data: + assert False, f"Missed {required_field} param in bucket configuration" + + data['provider'] = { + 'aws': CloudProvider.aws, + 'gcs': CloudProvider.gcs, + }[data['provider'].lower()] + + if (access_key := data.pop("access_key", None)) and (secret_key := data.pop("secret_key", None)): + data["credentials"] = S3BucketCredentials(access_key, secret_key) + + elif service_account_key := data.pop("service_account_key", None): + data["credentials"] = GCSCredentials(service_account_key) - return cls.from_parsed_url(parse_bucket_url(url)) + return BucketAccessInfo(**data) @classmethod - def from_parsed_url(cls, parsed_url: BucketUrl) -> BucketAccessInfo: - return BucketAccessInfo(url=parsed_url) + def parse_obj(cls, data: Union[Dict, str]) -> BucketAccessInfo: + if isinstance(data, Dict): + return cls.from_dict(data) - @property - def provider(self) -> CloudProviders: - return self.url.provider + return cls.from_url(data) diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py index 33b9a4f7ee..ec31417643 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py @@ -1,95 +1,41 @@ -from typing import Optional, Union, overload -from urllib.parse import urlparse +from typing import Optional -from src.core.config import Config from src.services.cloud.client import StorageClient -from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client -from src.services.cloud.types import BucketAccessInfo, BucketCredentials, BucketUrl, CloudProviders -from src.utils.net import is_ipv4 - - -def parse_bucket_url(data_url: str) -> BucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return BucketUrl( - provider=CloudProviders.aws, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return BucketUrl( - provider=CloudProviders.aws, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") +from src.services.cloud.gcs import GCSClient +from src.services.cloud.s3 import S3Client +from src.services.cloud.types import BucketAccessInfo, CloudProvider def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None + bucket_name: str, provider: CloudProvider, *, bucket_host: Optional[str] = None ) -> str: match provider: - case CloudProviders.aws: + case CloudProvider.aws: return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs: + case CloudProvider.gcs: return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" -@overload -def make_client(url: BucketUrl, credentials: Optional[BucketCredentials] = None) -> StorageClient: - ... - - -@overload def make_client( bucket_info: BucketAccessInfo, ) -> StorageClient: - ... - - -def make_client( - _pos1: Union[BucketUrl, BucketAccessInfo, None] = None, - *, - bucket_info: Optional[BucketAccessInfo] = None, - url: Optional[BucketUrl] = None, - credentials: Optional[BucketCredentials] = None, -) -> StorageClient: - if _pos1 is not None: - if isinstance(_pos1, BucketAccessInfo): - bucket_info = _pos1 - else: - url = _pos1 - - if bucket_info is None: - bucket_info = BucketAccessInfo(url=url, credentials=credentials) + client_kwargs = { + "bucket": bucket_info.bucket_name, + } match bucket_info.provider: - case CloudProviders.aws: + case CloudProvider.aws: client_kwargs = {} if bucket_info.credentials: client_kwargs["access_key"] = bucket_info.credentials.access_key client_kwargs["secret_key"] = bucket_info.credentials.secret_key - client = S3Client(bucket_info.url.host_url, **client_kwargs) + client = S3Client(bucket_info.host_url, **client_kwargs) + case CloudProvider.gcs: + if bucket_info.credentials: + client_kwargs["service_account_json"] = bucket_info.credentials.service_account_key + + client = GCSClient(**client_kwargs) case _: raise Exception("Unsupported cloud provider") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index a71ec3d0d4..434d73727b 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -25,23 +25,23 @@ def tearDown(self): self.client.delete_bucket(Bucket=self.bucket_name) def test_file_operations(self): - client = S3Client(self.url, access_key=self.access_key, secret_key=self.secret) + client = S3Client(self.url, bucket=self.bucket_name, access_key=self.access_key, secret_key=self.secret) - assert len(client.list_files(self.bucket_name)) == 0 + assert len(client.list_files()) == 0 file_name = "test_file" data = "this is a test".encode("utf-8") - assert not client.file_exists(self.bucket_name, file_name) - client.create_file(self.bucket_name, file_name, data) - assert client.file_exists(self.bucket_name, file_name) - assert len(client.list_files(self.bucket_name)) == 1 + assert not client.file_exists(file_name) + client.create_file(file_name, data) + assert client.file_exists(file_name) + assert len(client.list_files()) == 1 - file_content = client.download_fileobj(bucket=self.bucket_name, key=file_name) + file_content = client.download_fileobj(key=file_name) assert file_content == data - client.remove_file(self.bucket_name, file_name) - assert not client.file_exists(self.bucket_name, file_name) + client.remove_file(file_name) + assert not client.file_exists(file_name) def test_degenerate_file_operations(self): client = S3Client(self.url, access_key=self.access_key, secret_key=self.secret) @@ -49,25 +49,25 @@ def test_degenerate_file_operations(self): invalid_file = "non-existent-file" with pytest.raises(ClientError): - client.download_fileobj(bucket=invalid_bucket, key=invalid_file) + client.download_fileobj(invalid_file, bucket=invalid_bucket) with pytest.raises(ClientError): - client.download_fileobj(bucket=self.bucket_name, key=invalid_file) + client.download_fileobj(invalid_file, bucket=self.bucket_name) with pytest.raises(ClientError): - client.create_file(bucket=invalid_bucket, filename=invalid_file) + client.create_file(invalid_file, bucket=invalid_bucket) with pytest.raises(ClientError): client.list_files(bucket=invalid_bucket) - client.remove_file(bucket=self.bucket_name, filename=invalid_file) + client.remove_file(invalid_file, bucket=self.bucket_name) def test_degenerate_client(self): with pytest.raises(EndpointConnectionError): invalid_client = S3Client( "http://not.an.url:1234", access_key=self.access_key, secret_key=self.secret ) - invalid_client.create_file(self.bucket_name, "test.txt") + invalid_client.create_file("test.txt", bucket=self.bucket_name) with pytest.raises(ValueError): S3Client("nonsense-stuff") From 46601ae4cbd3c148ce7b007c775209ecb8d9691a Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 14:18:08 +0200 Subject: [PATCH 13/82] Remove local dev mocks --- .../cvat/exchange-oracle/src/chain/escrow.py | 21 ---------------- .../src/handlers/job_creation.py | 9 +------ .../src/handlers/job_export.py | 2 -- .../cvat/exchange-oracle/src/models/cvat.py | 2 +- .../exchange-oracle/src/schemas/webhook.py | 7 +++--- .../manifest_boxes_from_points_local.json | 24 ------------------ .../cvat/recording-oracle/src/chain/escrow.py | 25 ------------------- .../process_reputation_oracle_webhooks.py | 7 +++--- .../recording-oracle/src/schemas/webhook.py | 7 +++--- .../src/validators/signature.py | 3 --- 10 files changed, 11 insertions(+), 96 deletions(-) delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/manifests/manifest_boxes_from_points_local.json diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index ac8c8b2910..5a3886843b 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -1,4 +1,3 @@ -import datetime import json from typing import List @@ -10,26 +9,6 @@ def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: - # TODO: remove mock - if escrow_address.startswith("test-"): - from human_protocol_sdk.constants import ChainId - - return EscrowData( - chain_id=ChainId(chain_id), - id="test", - address=escrow_address, - amount_paid=10, - balance=10, - count=1, - factory_address="", - launcher="", - status="Pending", - token="HMT", - total_funded_amount=10, - created_at=datetime.datetime(2023, 1, 1), - manifest_url="http://127.0.0.1:9010/manifests/manifest_boxes_from_points_local.json", - ) - escrow = EscrowUtils.get_escrow(ChainId(chain_id), escrow_address) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index f6bfa99e62..ce8c19179b 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -695,9 +695,6 @@ def _prepare_label_configuration(self): self.label_configuration = make_label_configuration(self.manifest) def _upload_task_meta(self): - # TODO: maybe extract into a separate function / class / library, - # extract constants, serialization methods return TaskConfig from build() - layout = boxes_from_points_task.TaskMetaLayout() serializer = boxes_from_points_task.TaskMetaSerializer() @@ -839,11 +836,7 @@ def _create_on_cvat(self): # Register cloud storage on CVAT to pass user dataset cloud_storage = cvat_api.create_cloudstorage( CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[oracle_bucket.provider], - oracle_bucket.url.host_url.replace( - # TODO: remove mock - "127.0.0.1", - "172.22.0.1", - ), + oracle_bucket.url.host_url, oracle_bucket.url.bucket_name, # TODO: add # credentials=... diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index ab0f87ead7..ac6966908f 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -287,8 +287,6 @@ def postprocess_annotations( TaskType.image_boxes_from_points: _BoxesFromPointsTaskProcessor, } - # TODO: restore original filenames and merge skeletons from RoIs (skeletons from boxes task) - task_type = manifest.annotation.type processor = processor_classes[task_type]( escrow_address=escrow_address, diff --git a/packages/examples/cvat/exchange-oracle/src/models/cvat.py b/packages/examples/cvat/exchange-oracle/src/models/cvat.py index 0ab9d59224..2ffed4ccd6 100644 --- a/packages/examples/cvat/exchange-oracle/src/models/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/models/cvat.py @@ -28,7 +28,7 @@ class Project(Base): job_type = Column(String, Enum(TaskType), nullable=False) escrow_address = Column(String(42), unique=True, nullable=False) chain_id = Column(Integer, Enum(Networks), nullable=False) - bucket_url = Column(String, nullable=False) # TODO: consider removal + bucket_url = Column(String, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) cvat_webhook_id = Column(Integer, nullable=True) diff --git a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py index 20384c3e9d..ac3e7afcf4 100644 --- a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py @@ -14,10 +14,9 @@ class OracleWebhook(BaseModel): event_data: Optional[dict] = None timestamp: Optional[datetime] = None # TODO: remove optional - # TODO: remove mock - # @validator("escrow_address", allow_reuse=True) - # def validate_escrow_(cls, value): - # return validate_address(value) + @validator("escrow_address", allow_reuse=True) + def validate_escrow_(cls, value): + return validate_address(value) # pylint: disable=too-few-public-methods class Config: diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/manifests/manifest_boxes_from_points_local.json b/packages/examples/cvat/exchange-oracle/tests/utils/manifests/manifest_boxes_from_points_local.json deleted file mode 100644 index 0695e62387..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/manifests/manifest_boxes_from_points_local.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "data": { - "data_url": "http://127.0.0.1:9010/datasets/", - "points_url": "http://127.0.0.1:9010/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco%20keypoints%201.0-good/annotations/person_keypoints_default.json" - }, - "annotation": { - "labels": [ - { "name": "cat" }, - { "name": "dog" }, - { "name": "mouse" } - ], - "description": "Brief task description", - "user_guide": "Task description (markdown)", - "type": "IMAGE_BOXES_FROM_POINTS", - "job_size": 10, - "max_time": 1800 - }, - "validation": { - "min_quality": 0.8, - "val_size": 2, - "gt_url": "http://127.0.0.1:9010/datasets/gt_boxes/annotations/instances_default.json" - }, - "job_bounty": "0.05" -} diff --git a/packages/examples/cvat/recording-oracle/src/chain/escrow.py b/packages/examples/cvat/recording-oracle/src/chain/escrow.py index 7c325e1a74..e15c9d1f72 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/recording-oracle/src/chain/escrow.py @@ -1,4 +1,3 @@ -import datetime import json from typing import List @@ -11,26 +10,6 @@ def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: - # TODO: remove mock - if escrow_address.startswith("test-"): - from human_protocol_sdk.constants import ChainId - - return EscrowData( - chain_id=ChainId(chain_id), - id="test", - address=escrow_address, - amount_paid=10, - balance=10, - count=1, - factory_address="", - launcher="", - status="Pending", - token="HMT", - total_funded_amount=10, - created_at=datetime.datetime(2023, 1, 1), - manifest_url="http://127.0.0.1:9010/manifests/manifest_boxes_from_points_local.json", - ) - escrow = EscrowUtils.get_escrow(ChainId(chain_id), escrow_address) if not escrow: raise Exception(f"Can't find escrow {escrow_address}") @@ -83,10 +62,6 @@ def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: def store_results(chain_id: int, escrow_address: str, url: str, hash: str) -> None: - # TODO: remove mock - if escrow_address.startswith("test-"): - return - web3 = get_web3(chain_id) escrow_client = EscrowClient(web3) diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py index 5fb551ee3b..a9eb2d37a9 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py @@ -48,14 +48,13 @@ def process_outgoing_reputation_oracle_webhooks(): timestamp=None, # TODO: reputation oracle doesn't support ) - # FIXME: For a sake of compatability with the current + # TODO: remove compatibility code + # FIXME: For a sake of compatibility with the current # version of Reputation Oracle keep this + # vvv body["escrowAddress"] = body.pop("escrow_address") body["chainId"] = body.pop("chain_id") body["eventType"] = body.pop("event_type") - - # TODO: remove compatibility code - # vvv body.pop("event_data") # ^^^ diff --git a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py index 78dcb56586..e47a129847 100644 --- a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py @@ -14,10 +14,9 @@ class OracleWebhook(BaseModel): event_data: Optional[dict] = None timestamp: Optional[datetime] = None # TODO: remove optional - # TODO: restore - # @validator("escrow_address", allow_reuse=True) - # def validate_escrow_(cls, value): - # return validate_address(value) + @validator("escrow_address", allow_reuse=True) + def validate_escrow_(cls, value): + return validate_address(value) # pylint: disable=too-few-public-methods class Config: diff --git a/packages/examples/cvat/recording-oracle/src/validators/signature.py b/packages/examples/cvat/recording-oracle/src/validators/signature.py index b63cc683e1..deb3364a70 100644 --- a/packages/examples/cvat/recording-oracle/src/validators/signature.py +++ b/packages/examples/cvat/recording-oracle/src/validators/signature.py @@ -22,9 +22,6 @@ async def validate_oracle_webhook_signature( OracleWebhookTypes.exchange_oracle: exchange_oracle_address, } - # TODO: remove mock - return OracleWebhookTypes.exchange_oracle - matched_signer = next( ( s_type From ade7005b8563ef1b99ba301e23bf74ceefebd899 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 14:42:06 +0200 Subject: [PATCH 14/82] Remove some extra changes --- .../cvat/exchange-oracle/src/chain/escrow.py | 1 + .../tests/utils/datasets/gt_annotations_coco.zip | Bin 0 -> 1146 bytes .../annotations/instances_default.json | 1 + .../tests/utils/datasets/gt_boxes.zip | Bin 2851 -> 0 bytes .../gt_boxes/annotations/instances_default.json | 1 - ...24_01_24_11_59_58_coco keypoints 1.0-good.zip | Bin 5859 -> 0 bytes .../annotations/person_keypoints_default.json | 1 - ...2_01_10_coco keypoints 1.0-repeated-in-gt.zip | Bin 6483 -> 0 bytes .../annotations/person_keypoints_default.json | 1 - ...03_18_coco keypoints 1.0-invalid-category.zip | Bin 6763 -> 0 bytes .../annotations/person_keypoints_default.json | 1 - .../manifest_boxes.json => manifest.json} | 0 .../cvat/recording-oracle/src/chain/escrow.py | 1 + 13 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco.zip create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco/annotations/instances_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes.zip delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good.zip delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt.zip delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt/annotations/person_keypoints_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_03_18_coco keypoints 1.0-invalid-category.zip delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_03_18_coco keypoints 1.0-invalid-category/annotations/person_keypoints_default.json rename packages/examples/cvat/exchange-oracle/tests/utils/{manifests/manifest_boxes.json => manifest.json} (100%) diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 5a3886843b..f81b2103a5 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -57,6 +57,7 @@ def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( [parsed_url.path], bucket=parsed_url.bucket_name )[0] + return json.loads(manifest_content.decode("utf-8")) diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco.zip b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_annotations_coco.zip new file mode 100644 index 0000000000000000000000000000000000000000..5b335a0873072e493b76f8a5c59ca28ad61efb9c GIT binary patch literal 1146 zcmb_cy>8nu5O(UcPvE%&hYkh7k`(p(3SPW*C<23)Xql-@8YGp}Fg)~K`ZitrKs{Qr zgG7Liz zn{hz4uDia8B&}uV6-I8HLtu^2;}vnQLM&*;3X-uz41oC);v&sRR$!hnz=$~%Mib16 zlH|qzK%79;ui;qagF3Ps+EUEo8Ej<)4C=8zYDlM~h0B#$)X)yZg%j(ej8G6(*=oVa!#d2{Db?sQhsu|7dWb76(&AWa6-n0hrVNtM-H~aBYAJ}j2W3zII z4OU>(!MZ-I4wZA%c)@~@2Z?PjS{w8}hvm_NRTp5H;{)M}Xt??kE_XobEZ%tGnjY+F zBEzbmp~ef=_E7t4%ejG9XRtDw#*jFJDK)r(R5Vxyi8G8llv6q$E*yb_xIZZ$SBH!(>#wbH(D#Ag=qOgy6zQL4)WDtCYwT1Lj=c+N=KY} vk;b2QliRCTH`kZ1ug$#{etvxq|9ZRi!ew@H`{r%vc?F*dlz-qFJR1E2ne$^D diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json deleted file mode 100644 index 7773edb38d..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_boxes/annotations/instances_default.json +++ /dev/null @@ -1 +0,0 @@ -{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":""},{"id":2,"name":"dog","supercategory":""}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":34414.705200000004,"bbox":[337.96,209.86,272.01,126.52],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":40618.613400000024,"bbox":[856.68,261.52,214.03,189.78],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":47841.0129,"bbox":[247.29,404.9,159.21,300.49],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":4,"image_id":6,"category_id":2,"segmentation":[],"area":443166.7967999999,"bbox":[105.23,396.85,524.16,845.48],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}},{"id":5,"image_id":6,"category_id":2,"segmentation":[],"area":427665.63399999996,"bbox":[592.07,142.07,407.32,1049.95],"iscrowd":0,"attributes":{"occluded":false,"rotation":0.0}}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good.zip b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good.zip deleted file mode 100644 index cfd3b0162b4ed24fe351b0f810470d60c1168ffa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5859 zcmcJTQEuBf5Qg3DcAxj5*aPsb0Ktl+C{n&cUh=jJ6oFTzWzElzGn^Ug{_gFs???9M_rJ|w-@g9$^6`(+$i6;cE=tp^ zjLIZSi;wF#FS2y<9KWu!BrS`{ES^W3WywBcdP|ncG){}SAmdM4l18hTj0vG6nUS$a z$tGXg@$LaL={zIjEtzI%nJ15%GRyZLoke9lndULxaFe}x78lbzSzG=6L~VwWFIdcR z_BDZsaOjxSTJ^tb78o&>C$(3S)q=Cn zYl~1jt#Z*EgXes5+~R3A&6?5PInlb@h&fIU(~YU zscc{e=c@g|Vk3`RY))v6-S2Z*(Hkt{rB#-Pq6Js>rW^cBSAf3i)Q;^Nf1 zM*SF5ra##xO*z@IO*On-6*b|cU{{5>E`U|3nAC$q?K_N%c(IDpeVMoQ4pvZvk>eSH zLV?a+_BB2#_x0S?q%Fy&)8%GnYsEZT7BM9#vh%#^ z3#y4-HM4Ieeh?5aoo-f5vw@;^QgLp^3AN7@m0B`C9D>HN-vxsJxfw=E&4i#TFl-Rl zIQF|>P#zl?oH!@YKzd1_NsYkbR4K#Iw+s+yu0R(KYFvYZlM{3OiMY)h3Q-kESGlec7G&T(oV(%Ittpg?k>dTNBHAJ|%0$n(^VY-<|aySOb zO(Ncgz;Wz%(ja{`G&o9*cP`CsLCBP#zQ9uG(0#<*YM~-Gfz@3IcLEj z$LCC;lgQ}|aF`sT3)8jsG}GwXlxAEG5pb?RCy&z{*wb_kkPNE&qm6H?>JZ(5BhU%t zvRhIZV=a1X_U}cK3I0etG})=g*z12h#ZS f|G)OXlP|U1PoeMc-u-58c!S?R@%{ymu}7othUOe+ diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json deleted file mode 100644 index a45e8dfeb0..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_11_59_58_coco keypoints 1.0-good/annotations/person_keypoints_default.json +++ /dev/null @@ -1 +0,0 @@ -{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":"","keypoints":["1"],"skeleton":[]},{"id":3,"name":"dog","supercategory":"","keypoints":["1"],"skeleton":[]},{"id":5,"name":"mouse","supercategory":"","keypoints":["1"],"skeleton":[]}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":0.0,"bbox":[500.95,287.88,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[500.95,287.88,2],"num_keypoints":1},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":0.0,"bbox":[974.5,373.49,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[974.5,373.49,2],"num_keypoints":1},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":0.0,"bbox":[337.22,568.8,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[337.22,568.8,2],"num_keypoints":1},{"id":4,"image_id":2,"category_id":3,"segmentation":[],"area":0.0,"bbox":[370.01,558.54,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[370.01,558.54,2],"num_keypoints":1},{"id":5,"image_id":3,"category_id":1,"segmentation":[],"area":0.0,"bbox":[408.27,331.58,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[408.27,331.58,2],"num_keypoints":1},{"id":6,"image_id":4,"category_id":3,"segmentation":[],"area":0.0,"bbox":[514.42,258.32,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[514.42,258.32,2],"num_keypoints":1},{"id":7,"image_id":4,"category_id":3,"segmentation":[],"area":0.0,"bbox":[485.23,695.62,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[485.23,695.62,2],"num_keypoints":1},{"id":8,"image_id":5,"category_id":3,"segmentation":[],"area":0.0,"bbox":[377.65,1230.59,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[377.65,1230.59,2],"num_keypoints":1},{"id":9,"image_id":6,"category_id":3,"segmentation":[],"area":0.0,"bbox":[776.26,439.85,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[776.26,439.85,2],"num_keypoints":1},{"id":10,"image_id":6,"category_id":3,"segmentation":[],"area":0.0,"bbox":[310.51,535.6,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[310.51,535.6,2],"num_keypoints":1},{"id":11,"image_id":7,"category_id":1,"segmentation":[],"area":0.0,"bbox":[469.52,401.96,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[469.52,401.96,2],"num_keypoints":1},{"id":12,"image_id":8,"category_id":1,"segmentation":[],"area":0.0,"bbox":[767.03,1311.69,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[767.03,1311.69,2],"num_keypoints":1},{"id":13,"image_id":9,"category_id":3,"segmentation":[],"area":0.0,"bbox":[707.75,257.13,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[707.75,257.13,2],"num_keypoints":1},{"id":14,"image_id":10,"category_id":3,"segmentation":[],"area":0.0,"bbox":[364.42,784.27,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[364.42,784.27,2],"num_keypoints":1},{"id":15,"image_id":11,"category_id":3,"segmentation":[],"area":0.0,"bbox":[896.64,491.84,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[896.64,491.84,2],"num_keypoints":1},{"id":16,"image_id":11,"category_id":3,"segmentation":[],"area":0.0,"bbox":[567.3,341.49,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[567.3,341.49,2],"num_keypoints":1},{"id":17,"image_id":12,"category_id":1,"segmentation":[],"area":0.0,"bbox":[774.69,418.53,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[774.69,418.53,2],"num_keypoints":1},{"id":18,"image_id":13,"category_id":1,"segmentation":[],"area":0.0,"bbox":[490.89,369.36,0.0,0.0],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[490.89,369.36,2],"num_keypoints":1}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt.zip b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-points-for-boxes_annotations_2024_01_24_12_01_10_coco keypoints 1.0-repeated-in-gt.zip deleted file mode 100644 index 163e5bfcdd9713fd93f2d0e9790b6e34b6399540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6483 zcmcJUPm-fH6vjQ7Bqx|f*4ibdQjmm@@D+N~yQFHW%R}4P&9n_Jz?pQpyvRwiNDh!w z2 z$|OsRpSN*dWa;cRe%oeAS{AcKyo`41l6}VTo~)C3oEC9GrXTktjW#iv5<*F`AXAT$ zUB0%*hbN4r%ZyC-WS*sEo;>f$EI+<=5tZ?5p2v8?N%rJLT+H)iYxR!WiR9=TDhEGl12FfVxdDyUgBi+Qj)1rJ|)X!9nVftNj59a zK5s3;DQH!QmN;DEB0m&{p*ZlSM2} zvSCv;um^3`;b3tgFPGRfXpOzzZCNoGEb^sOmWQGPR}Q8dd`(w`SW=63I9%P3brTs; zZ>bIkYZ|J}*b1TXQcKn!>>49+x?|h5fz)8K9EsDd+m%znaJs0*ow{9HNDn7lmnklq z(KYJ-Ic0{Et;>{C9lKP+`&Hoy4a2UC_+)^URm@HYXYV^77x8Knr$?K&eur6L!l>~K zN%H)h{f@y<`%F=(CG$gSU%;z^Yw0FtzsFii@u&~ zf@WgvSDZ3>;UW4+m~uY$ONg z%IGNWGha}F=twa}bX7~A!!gQ7L0=93iRRFcB$q;VYnqcQfn3{%7yfp z8pTCh3cWE=5Ey*y5DfZBZvj}A2R zB;}HG7K~C4S_-{HPJ4jz?GRmQK-xpshF%oS8<%5boGZ}FVoF3s;AcajNsr30sA55M3`*7@6akT~U zc7uIojnnv?jauMyG}z@4or){_FQS lsv98q^YqinFc`?eV4vKSMzp7 z4`um>8d)%GDS_jNwt4vRUfpwL-QB+b@xzV(`S(xv@sH8u@l*QpTx5B<9#7L*vRRbuGlpNuBAcZ7I$e{|r&p3E%an`=p(LA|s+DtL37Y}}Q|FuIp#_1d9XZa`I8wz-3#vuLj2Z$b z6zIZ1id>DOqllp-3ul>gL*Rr0T{y7Ft8rqhn5NQ7rs5%Jg892(;Br^PNNJfssu{-y z2~9A67Yy9|Gz>^jl>rwk!;F-1{j;wLPAVojHIZc6QH~?0s4-0HJ`013BZe-ZRuMEd zG2@yV?Yy|4yH#K~mb7I9y4(1n9W=zxPN65yN}9nBR}f(oA-VhEg2pbN)0 zSXc8XrrdWeh*1v+_xPNKJ!mL&5(WJM>?j~Q zGv9243nX;nxK847h=dCTI(dQy!TZrAK+;r}YMt_p!T=pYD9{NMvHMc~a2`^*|=aq)**pg210Va}jH zpc5$Q5O6++33n!t!UYU0Rid=YVTyJj& dict: manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( [parsed_url.path], bucket=parsed_url.bucket_name )[0] + return json.loads(manifest_content.decode("utf-8")) From c2429c3e521203785f640e246f636f854a5ea45c Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 17:13:02 +0200 Subject: [PATCH 15/82] Update tests --- .../tests/integration/chain/test_escrow.py | 28 +++------- .../tests/integration/chain/test_kvstore.py | 16 ++---- .../test_retrieve_annotations.py | 49 +++++++++++++++--- .../state_trackers/test_track_assignments.py | 2 +- .../test_process_job_launcher_webhooks.py | 51 ++++++++----------- .../test_process_recording_oracle_webhooks.py | 24 +++------ 6 files changed, 81 insertions(+), 89 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index 2e44ce7e2f..ea05f7d0a4 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -55,9 +55,7 @@ def test_validate_escrow_invalid_address(self): with self.assertRaises(EscrowClientError) as error: validate_escrow(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_validate_escrow_invalid_status(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: @@ -94,9 +92,7 @@ def test_get_escrow_manifest(self): def test_get_escrow_manifest_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_escrow_manifest(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: @@ -108,9 +104,7 @@ def test_get_job_launcher_address(self): def test_get_job_launcher_address_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_job_launcher_address(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_address_invalid_chain_id(self): with self.assertRaises(ValueError) as error: @@ -122,26 +116,20 @@ def test_get_job_launcher_address_empty_escrow(self): mock_function.return_value = None with self.assertRaises(Exception) as error: get_job_launcher_address(chain_id, escrow_address) - self.assertEqual( - f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception) - ) + self.assertEqual(f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception)) def test_get_recording_oracle_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_function.return_value = self.escrow_data - recording_oracle_address = get_recording_oracle_address( - chain_id, escrow_address - ) + recording_oracle_address = get_recording_oracle_address(chain_id, escrow_address) self.assertIsInstance(recording_oracle_address, str) self.assertEqual(recording_oracle_address, RECORDING_ORACLE_ADDRESS) def test_get_recording_oracle_address_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_recording_oracle_address(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_recording_oracle_address_invalid_chain_id(self): with self.assertRaises(ValueError) as error: @@ -153,6 +141,4 @@ def test_get_recording_oracle_address_empty_escrow(self): mock_function.return_value = None with self.assertRaises(Exception) as error: get_recording_oracle_address(chain_id, escrow_address) - self.assertEqual( - f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception) - ) + self.assertEqual(f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception)) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py index fdd3035f60..e24f3ca0d7 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py @@ -52,9 +52,7 @@ def test_get_job_launcher_url(self): def test_get_job_launcher_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: get_job_launcher_url(self.w3.eth.chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( @@ -72,17 +70,13 @@ def test_get_recording_oracle_url(self): self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) - recording_url = get_recording_oracle_url( - self.w3.eth.chain_id, escrow_address - ) + recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) self.assertEqual(recording_url, DEFAULT_URL) def test_get_recording_oracle_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: get_recording_oracle_url(self.w3.eth.chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_recording_oracle_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( @@ -91,7 +85,5 @@ def test_get_recording_oracle_url_invalid_recording_address(self): self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data mock_leader.return_value = MagicMock(webhook_url="") - recording_url = get_recording_oracle_url( - self.w3.eth.chain_id, escrow_address - ) + recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) self.assertEqual(recording_url, "") diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py index 600a30a624..bf467e09aa 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py @@ -1,10 +1,16 @@ +import io import json +import os import unittest import uuid +import zipfile from datetime import datetime, timedelta -from io import RawIOBase +from glob import glob +from tempfile import TemporaryDirectory from unittest.mock import Mock, patch +import datumaro as dm + from src.core.types import ( ExchangeOracleEventType, JobStatuses, @@ -15,7 +21,7 @@ ) from src.crons.state_trackers import retrieve_annotations from src.db import SessionLocal -from src.models.cvat import Assignment, Job, Project, Task, User +from src.models.cvat import Assignment, Image, Job, Project, Task, User from src.models.webhook import Webhook @@ -42,6 +48,14 @@ def test_retrieve_annotations(self): ) self.session.add(cvat_project) + project_images = ["sample1.jpg", "sample2.png"] + for image_filename in project_images: + self.session.add( + Image( + id=str(uuid.uuid4()), cvat_project_id=cvat_project_id, filename=image_filename + ) + ) + cvat_task_id = 1 cvat_task = Task( id=str(uuid.uuid4()), @@ -86,14 +100,37 @@ def test_retrieve_annotations(self): with ( open("tests/utils/manifest.json") as data, patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), + patch("src.crons.state_trackers.cvat_api") as mock_cvat_api, patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client.S3Client") as mock_S3Client, + patch("src.crons.state_trackers.cloud_client") as mock_cloud_client, ): manifest = json.load(data) mock_get_manifest.return_value = manifest - mock_create_file = Mock() - mock_S3Client.return_value.create_file = mock_create_file + + dummy_zip_file = io.BytesIO() + with zipfile.ZipFile(dummy_zip_file, "w") as archive, TemporaryDirectory() as tempdir: + mock_dataset = dm.Dataset( + media_type=dm.Image, + categories={ + dm.AnnotationType.label: dm.LabelCategories.from_iterable(["cat", "dog"]) + }, + ) + for image_filename in project_images: + mock_dataset.put(dm.DatasetItem(id=os.path.splitext(image_filename)[0])) + mock_dataset.export(tempdir, format="coco_instances") + + for filename in list(glob(os.path.join(tempdir, "**/*"), recursive=True)): + archive.write(filename, os.path.relpath(filename, tempdir)) + dummy_zip_file.seek(0) + + mock_cvat_api.get_job_annotations.return_value = dummy_zip_file + mock_cvat_api.get_project_annotations.return_value = dummy_zip_file + + mock_storage_client = Mock() + mock_storage_client.create_file = Mock() + mock_storage_client.list_files = Mock(return_value=[]) + mock_cloud_client.make_client = Mock(return_value=mock_storage_client) + mock_cloud_client.S3Client = Mock(return_value=mock_storage_client) retrieve_annotations() diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py index 10a5b8b772..5d339072b1 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py @@ -85,7 +85,7 @@ def test_track_expired_assignments(self): # TODO: # Fix src/crons/state_trackers.py # Where in `cvat_service.get_active_assignments()` return value will be empty - # because it actually looking for the expired assignments + # because it actually looking for the expired assignments # def test_track_canceled_assignments(self): # (_, _, cvat_job) = create_project_task_and_job( diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index f51bf3ddc1..3408253dfa 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -57,10 +57,10 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): with ( patch("src.chain.escrow.get_escrow") as mock_escrow, open("tests/utils/manifest.json") as data, - patch("src.cvat.tasks.get_escrow_manifest") as mock_get_manifest, - patch("src.cvat.tasks.cvat_api") as mock_cvat_api, - patch("src.cvat.tasks.cloud_service") as mock_cloud_service, - patch("src.cvat.tasks.get_gt_filenames") as mock_gt_filenames, + patch("src.handlers.job_creation.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.job_creation.cvat_api") as mock_cvat_api, + patch("src.handlers.job_creation.cloud_service.make_client") as mock_make_cloud_client, + patch("src.handlers.job_creation.get_gt_filenames") as mock_gt_filenames, ): manifest = json.load(data) mock_get_manifest.return_value = manifest @@ -77,14 +77,15 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): "image2.jpg", ] mock_gt_filenames.return_value = filenames - mock_cloud_service.list_files.return_value = filenames + + mock_cloud_client = Mock() + mock_cloud_client.list_filenames.return_value = filenames + mock_make_cloud_client.return_value = mock_cloud_client process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -193,12 +194,12 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ with ( patch("src.chain.escrow.get_escrow") as mock_escrow, open("tests/utils/manifest.json") as data, - patch("src.cvat.tasks.get_escrow_manifest") as mock_get_manifest, - patch("src.cvat.tasks.cvat_api") as mock_cvat_api, - patch("src.cvat.tasks.cloud_service"), - patch("src.cvat.tasks.get_gt_filenames"), + patch("src.handlers.job_creation.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.job_creation.cvat_api") as mock_cvat_api, + patch("src.handlers.job_creation.cloud_service"), + patch("src.handlers.job_creation.get_gt_filenames"), patch( - "src.cvat.tasks.db_service.add_project_images", + "src.handlers.job_creation.db_service.add_project_images", side_effect=Exception("Error"), ), ): @@ -216,9 +217,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -268,9 +267,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -321,9 +318,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -375,9 +370,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -425,9 +418,7 @@ def test_process_outgoing_job_launcher_webhooks(self): process_outgoing_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -455,9 +446,7 @@ def test_process_outgoing_job_launcher_webhooks_invalid_type(self): process_outgoing_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py index b53efc692d..1ddf051f77 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py @@ -70,9 +70,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -115,9 +113,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -180,9 +176,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -236,9 +230,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -283,9 +275,7 @@ def test_process_outgoing_recording_oracle_webhooks(self): process_outgoing_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -313,9 +303,7 @@ def test_process_outgoing_recording_oracle_webhooks_invalid_type(self): process_outgoing_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) From ad3ffc1460b91267a862d61f735b145f9f06f2fe Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 18:30:42 +0200 Subject: [PATCH 16/82] Update some tests --- .../tests/integration/chain/test_escrow.py | 4 ++-- .../tests/integration/chain/test_kvstore.py | 12 ++++++------ .../cron/test_process_job_launcher_webhooks.py | 4 ++-- .../cron/test_process_recording_oracle_webhooks.py | 4 ++-- .../cvat/exchange-oracle/tests/utils/constants.py | 3 ++- .../tests/integration/chain/test_escrow.py | 8 ++++---- .../tests/integration/chain/test_kvstore.py | 10 +++++----- .../services/cloud/test_client_service.py | 8 ++++---- .../cvat/recording-oracle/tests/utils/constants.py | 3 ++- .../recording-oracle/tests/utils/setup_escrow.py | 6 +++--- 10 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index ea05f7d0a4..5aa045f3a3 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -14,7 +14,7 @@ ) from tests.utils.constants import ( - DEFAULT_URL, + DEFAULT_MANIFEST_URL, ESCROW_ADDRESS, FACTORY_ADDRESS, JOB_LAUNCHER_ADDRESS, @@ -41,7 +41,7 @@ def setUp(self): token=TOKEN_ADDRESS, total_funded_amount=1000, created_at="", - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, recording_oracle=RECORDING_ORACLE_ADDRESS, ) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py index e24f3ca0d7..3d8b5e0976 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py @@ -8,7 +8,7 @@ from src.chain.kvstore import get_job_launcher_url, get_recording_oracle_url from tests.utils.constants import ( - DEFAULT_URL, + DEFAULT_MANIFEST_URL, ESCROW_ADDRESS, FACTORY_ADDRESS, JOB_LAUNCHER_ADDRESS, @@ -36,7 +36,7 @@ def setUp(self): token=TOKEN_ADDRESS, total_funded_amount=1000, created_at="", - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, recording_oracle=RECORDING_ORACLE_ADDRESS, ) @@ -45,9 +45,9 @@ def test_get_job_launcher_url(self): "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: mock_escrow.return_value = self.escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) recording_url = get_job_launcher_url(self.w3.eth.chain_id, escrow_address) - self.assertEqual(recording_url, DEFAULT_URL) + self.assertEqual(recording_url, DEFAULT_MANIFEST_URL) def test_get_job_launcher_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: @@ -69,9 +69,9 @@ def test_get_recording_oracle_url(self): ) as mock_leader: self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) - self.assertEqual(recording_url, DEFAULT_URL) + self.assertEqual(recording_url, DEFAULT_MANIFEST_URL) def test_get_recording_oracle_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index 3408253dfa..29cc14e4b5 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -26,7 +26,7 @@ from src.models.webhook import Webhook from src.services.webhook import OracleWebhookDirectionTag -from tests.utils.constants import DEFAULT_URL, JOB_LAUNCHER_ADDRESS +from tests.utils.constants import DEFAULT_MANIFEST_URL, JOB_LAUNCHER_ADDRESS escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value @@ -410,7 +410,7 @@ def test_process_outgoing_job_launcher_webhooks(self): mock_escrow_data = Mock() mock_escrow_data.launcher = JOB_LAUNCHER_ADDRESS mock_escrow.return_value = mock_escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) mock_response = MagicMock() mock_response.raise_for_status.return_value = None mock_httpx_post.return_value = mock_response diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py index 1ddf051f77..e7f534cc90 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py @@ -25,7 +25,7 @@ from src.models.webhook import Webhook from src.services.webhook import OracleWebhookDirectionTag -from tests.utils.constants import DEFAULT_URL, RECORDING_ORACLE_ADDRESS +from tests.utils.constants import DEFAULT_MANIFEST_URL, RECORDING_ORACLE_ADDRESS escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value @@ -267,7 +267,7 @@ def test_process_outgoing_recording_oracle_webhooks(self): mock_escrow_data = Mock() mock_escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = mock_escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) mock_response = MagicMock() mock_response.raise_for_status.return_value = None mock_httpx_post.return_value = mock_response diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/constants.py b/packages/examples/cvat/exchange-oracle/tests/utils/constants.py index 33e16d146e..2c3655e57f 100644 --- a/packages/examples/cvat/exchange-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/exchange-oracle/tests/utils/constants.py @@ -5,6 +5,7 @@ RECORDING_ORACLE_PRIV = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" RECORDING_ORACLE_FEE = 10 +REPUTATION_ORACLE_WEBHOOK_URL = "http://localhost:5001/webhook/cvat" REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_FEE = 10 @@ -17,7 +18,7 @@ TOKEN_ADDRESS = "0x976EA74026E726554dB657fA54763abd0C3a0aa9" FACTORY_ADDRESS = "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" -DEFAULT_URL = "http://host.docker.internal:9000/manifests/manifest.json" +DEFAULT_MANIFEST_URL = "http://host.docker.internal:9000/manifests/manifest.json" DEFAULT_HASH = "test" SIGNATURE = "0xa0c5626301e3c198cb91356e492890c0c28db8c37044846134939246911a693c4d7116d04aa4bc40a41077493868b8dd533d30980f6addb28d1b3610a84cb4091c" diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index eea670e687..5cafd8ec8c 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -19,7 +19,7 @@ from tests.utils.constants import ( DEFAULT_GAS_PAYER_PRIV, DEFAULT_HASH, - DEFAULT_URL, + DEFAULT_MANIFEST_URL, REPUTATION_ORACLE_ADDRESS, ) from tests.utils.setup_escrow import ( @@ -108,13 +108,13 @@ def test_store_results(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 results = store_results( - self.w3.eth.chain_id, escrow_address, DEFAULT_URL, DEFAULT_HASH + self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, DEFAULT_HASH ) self.assertIsNone(results) intermediate_results_url = get_intermediate_results_url( self.w3, escrow_address ) - self.assertEqual(intermediate_results_url, DEFAULT_URL) + self.assertEqual(intermediate_results_url, DEFAULT_MANIFEST_URL) def test_store_results_invalid_url(self): escrow_address = create_escrow(self.w3) @@ -131,7 +131,7 @@ def test_store_results_invalid_hash(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 with self.assertRaises(EscrowClientError) as error: - store_results(self.w3.eth.chain_id, escrow_address, DEFAULT_URL, "") + store_results(self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, "") self.assertEqual(f"Invalid empty hash", str(error.exception)) def test_get_reputation_oracle_address(self): diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py index 4f99224538..dc8ff0bb0a 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py @@ -9,7 +9,7 @@ from src.chain.kvstore import get_reputation_oracle_url, get_role_by_address -from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, DEFAULT_URL, REPUTATION_ORACLE_ADDRESS +from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, DEFAULT_MANIFEST_URL, REPUTATION_ORACLE_ADDRESS from tests.utils.setup_escrow import create_escrow from tests.utils.setup_kvstore import store_kvstore_value @@ -28,7 +28,7 @@ def setUp(self): def test_get_reputation_oracle_url(self): escrow_address = create_escrow(self.w3) - store_kvstore_value("webhook_url", DEFAULT_URL) + store_kvstore_value("webhook_url", DEFAULT_MANIFEST_URL) with ( patch("src.chain.kvstore.get_web3") as mock_get_web3, @@ -39,12 +39,12 @@ def test_get_reputation_oracle_url(self): mock_escrow = Mock() mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) reputation_url = get_reputation_oracle_url( self.w3.eth.chain_id, escrow_address ) - self.assertEqual(reputation_url, DEFAULT_URL) + self.assertEqual(reputation_url, DEFAULT_MANIFEST_URL) def test_get_reputation_oracle_url_invalid_escrow(self): with patch("src.chain.kvstore.get_web3") as mock_function: @@ -62,7 +62,7 @@ def test_get_reputation_oracle_url_invalid_address(self): ): mock_get_web3.return_value = self.w3 mock_escrow = Mock() - mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS + mock_escrow.reputation_oracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow mock_leader.return_value = MagicMock(webhook_url="") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index a71ec3d0d4..4ec2d2f3a3 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -4,7 +4,7 @@ import pytest from botocore.exceptions import ClientError, EndpointConnectionError -from src.services.cloud import S3Client +from src.services.cloud.s3 import S3Client class ServiceIntegrationTest(unittest.TestCase): @@ -37,7 +37,7 @@ def test_file_operations(self): assert client.file_exists(self.bucket_name, file_name) assert len(client.list_files(self.bucket_name)) == 1 - file_content = client.download_fileobj(bucket=self.bucket_name, key=file_name) + file_content = client.download_file(bucket=self.bucket_name, key=file_name) assert file_content == data client.remove_file(self.bucket_name, file_name) @@ -49,10 +49,10 @@ def test_degenerate_file_operations(self): invalid_file = "non-existent-file" with pytest.raises(ClientError): - client.download_fileobj(bucket=invalid_bucket, key=invalid_file) + client.download_file(bucket=invalid_bucket, key=invalid_file) with pytest.raises(ClientError): - client.download_fileobj(bucket=self.bucket_name, key=invalid_file) + client.download_file(bucket=self.bucket_name, key=invalid_file) with pytest.raises(ClientError): client.create_file(bucket=invalid_bucket, filename=invalid_file) diff --git a/packages/examples/cvat/recording-oracle/tests/utils/constants.py b/packages/examples/cvat/recording-oracle/tests/utils/constants.py index 07dde21a4a..e5ff716440 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/constants.py @@ -4,6 +4,7 @@ RECORDING_ORACLE_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" RECORDING_ORACLE_FEE = 10 +REPUTATION_ORACLE_WEBHOOK_URL = 'http://localhost:5001/webhook/cvat' REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_PRIV = "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" REPUTATION_ORACLE_FEE = 10 @@ -11,7 +12,7 @@ EXCHANGE_ORACLE_ADDRESS = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" EXCHANGE_ORACLE_FEE = 10 -DEFAULT_URL = "http://host.docker.internal:9000/manifests/manifest.json" +DEFAULT_MANIFEST_URL = "http://host.docker.internal:9000/manifests/manifest.json" DEFAULT_HASH = "test" SIGNATURE = "0xa0c5626301e3c198cb91356e492890c0c28db8c37044846134939246911a693c4d7116d04aa4bc40a41077493868b8dd533d30980f6addb28d1b3610a84cb4091c" diff --git a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py index a225373349..17af89c712 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py @@ -7,7 +7,7 @@ from tests.utils.constants import ( DEFAULT_HASH, - DEFAULT_URL, + DEFAULT_MANIFEST_URL, EXCHANGE_ORACLE_ADDRESS, EXCHANGE_ORACLE_FEE, JOB_REQUESTER_ID, @@ -36,7 +36,7 @@ def create_escrow(web3: Web3): recording_oracle_fee=RECORDING_ORACLE_FEE, reputation_oracle_address=REPUTATION_ORACLE_ADDRESS, reputation_oracle_fee=REPUTATION_ORACLE_FEE, - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, hash=DEFAULT_HASH, ), ) @@ -50,7 +50,7 @@ def fund_escrow(web3: Web3, escrow_address: str): def bulk_payout(web3: Web3, escrow_address: str, recipient: str, amount: Decimal): escrow_client = EscrowClient(web3) - escrow_client.bulk_payout(escrow_address, [recipient], [amount], DEFAULT_URL, DEFAULT_HASH, 1) + escrow_client.bulk_payout(escrow_address, [recipient], [amount], DEFAULT_MANIFEST_URL, DEFAULT_HASH, 1) def get_intermediate_results_url(web3: Web3, escrow_address: str): From 475401b2bea4c780bd7bf70401cbc1dc17c2bb33 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 12 Feb 2024 13:41:07 +0200 Subject: [PATCH 17/82] Remove extra launcher change --- .../job-launcher/server/src/modules/webhook/webhook.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts b/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts index 424fb59f55..d3510b9268 100644 --- a/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts +++ b/packages/apps/job-launcher/server/src/modules/webhook/webhook.entity.ts @@ -34,7 +34,7 @@ export class WebhookEntity extends BaseEntity { public hasSignature: boolean; @Column({ type: 'int' }) - public retriesCount = 0; + public retriesCount: number = 0; @Column({ type: 'timestamptz' }) public waitUntil: Date = new Date(); From 597be93ce0fe71690c7d0393dc47c3ad5b19d6ac Mon Sep 17 00:00:00 2001 From: maya Date: Fri, 16 Feb 2024 13:57:03 +0100 Subject: [PATCH 18/82] Refactor code --- .../cvat/exchange-oracle/src/.env.template | 4 +- .../cvat/exchange-oracle/src/core/config.py | 52 ++++++++---- .../src/crons/state_trackers.py | 14 ++-- .../src/handlers/job_creation.py | 6 +- .../src/handlers/job_export.py | 9 +-- .../src/services/cloud/client.py | 4 +- .../exchange-oracle/src/services/cloud/gcs.py | 17 +--- .../exchange-oracle/src/services/cloud/s3.py | 13 ++- .../src/services/cloud/types.py | 81 ++++++++++++++++--- .../src/services/cloud/utils.py | 17 ++-- .../cvat/recording-oracle/src/.env.template | 4 + .../cvat/recording-oracle/src/core/config.py | 81 +++++++++++-------- .../handlers/process_intermediate_results.py | 5 +- .../src/handlers/validation.py | 10 +-- .../src/services/cloud/client.py | 4 +- .../src/services/cloud/gcs.py | 17 +--- .../recording-oracle/src/services/cloud/s3.py | 12 +-- .../src/services/cloud/types.py | 81 ++++++++++++++++--- .../src/services/cloud/utils.py | 14 ++-- .../services/cloud/test_client_service.py | 8 +- 20 files changed, 287 insertions(+), 166 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/.env.template b/packages/examples/cvat/exchange-oracle/src/.env.template index ee8dba1feb..8534ae4ee7 100644 --- a/packages/examples/cvat/exchange-oracle/src/.env.template +++ b/packages/examples/cvat/exchange-oracle/src/.env.template @@ -50,12 +50,14 @@ CVAT_INCOMING_WEBHOOKS_URL= CVAT_WEBHOOK_SECRET= CVAT_ORG_SLUG= -# S3 Storage Config +# Storage Config (S3/GCS) +STORAGE_PROVIDER= STORAGE_ENDPOINT_URL= STORAGE_REGION= STORAGE_ACCESS_KEY= STORAGE_SECRET_KEY= +STORAGE_KEY_FILE_PATH= STORAGE_RESULTS_BUCKET_NAME= STORAGE_USE_SSL= diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index 4db35f343e..0ea1c16115 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -1,9 +1,10 @@ # pylint: disable=too-few-public-methods,missing-class-docstring """ Project configuration from env vars """ import os +from typing import ClassVar, Optional -from dotenv import load_dotenv from attrs.converters import to_bool +from dotenv import load_dotenv from src.utils.logging import parse_log_level from src.utils.net import is_ipv4 @@ -45,14 +46,18 @@ class LocalhostConfig: "LOCALHOST_PRIVATE_KEY", "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) - addr = os.environ.get("LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + addr = os.environ.get( + "LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ) job_launcher_url = os.environ.get("LOCALHOST_JOB_LAUNCHER_URL") recording_oracle_url = os.environ.get("LOCALHOST_RECORDING_ORACLE_URL") class CronConfig: - process_job_launcher_webhooks_int = int(os.environ.get("PROCESS_JOB_LAUNCHER_WEBHOOKS_INT", 30)) + process_job_launcher_webhooks_int = int( + os.environ.get("PROCESS_JOB_LAUNCHER_WEBHOOKS_INT", 30) + ) process_job_launcher_webhooks_chunk_size = os.environ.get( "PROCESS_JOB_LAUNCHER_WEBHOOKS_CHUNK_SIZE", 5 ) @@ -62,16 +67,24 @@ class CronConfig: process_recording_oracle_webhooks_chunk_size = os.environ.get( "PROCESS_RECORDING_ORACLE_WEBHOOKS_CHUNK_SIZE", 5 ) - track_completed_projects_int = int(os.environ.get("TRACK_COMPLETED_PROJECTS_INT", 30)) - track_completed_projects_chunk_size = os.environ.get("TRACK_COMPLETED_PROJECTS_CHUNK_SIZE", 5) + track_completed_projects_int = int( + os.environ.get("TRACK_COMPLETED_PROJECTS_INT", 30) + ) + track_completed_projects_chunk_size = os.environ.get( + "TRACK_COMPLETED_PROJECTS_CHUNK_SIZE", 5 + ) track_completed_tasks_int = int(os.environ.get("TRACK_COMPLETED_TASKS_INT", 30)) - track_creating_tasks_chunk_size = os.environ.get("TRACK_CREATING_TASKS_CHUNK_SIZE", 5) + track_creating_tasks_chunk_size = os.environ.get( + "TRACK_CREATING_TASKS_CHUNK_SIZE", 5 + ) track_creating_tasks_int = int(os.environ.get("TRACK_CREATING_TASKS_INT", 300)) track_assignments_int = int(os.environ.get("TRACK_ASSIGNMENTS_INT", 5)) track_assignments_chunk_size = os.environ.get("TRACK_ASSIGNMENTS_CHUNK_SIZE", 10) retrieve_annotations_int = int(os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60)) - retrieve_annotations_chunk_size = os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5) + retrieve_annotations_chunk_size = os.environ.get( + "RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5 + ) class CvatConfig: @@ -89,14 +102,22 @@ class CvatConfig: class StorageConfig: - endpoint_url = os.environ.get("STORAGE_ENDPOINT_URL", "storage.googleapis.com") - region = os.environ.get("STORAGE_REGION", "") - access_key = os.environ.get("STORAGE_ACCESS_KEY", "") - secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - data_bucket_name = os.environ.get("STORAGE_DATA_BUCKET_NAME", "") - results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") - secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) - # TODO: GCS key file + # common attributes + provider: ClassVar[str] = os.environ["STORAGE_PROVIDER"].lower() + data_bucket_name: ClassVar[str] = os.environ["STORAGE_RESULTS_BUCKET_NAME"] + endpoint_url: ClassVar[str] = os.environ[ + "STORAGE_ENDPOINT_URL" + ] # TODO: probably should be optional + region: ClassVar[Optional[str]] = os.environ.get("STORAGE_REGION") + results_dir_suffix: ClassVar[str] = os.environ.get( + "STORAGE_RESULTS_DIR_SUFFIX", "-results" + ) + secure: ClassVar[str] = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + # S3 specific attributes + access_key: ClassVar[Optional[str]] = os.environ.get("STORAGE_ACCESS_KEY") + secret_key: ClassVar[Optional[str]] = os.environ.get("STORAGE_SECRET_KEY") + # GCS specific attributes + key_file_path: ClassVar[Optional[str]] = os.environ.get("STORAGE_KEY_FILE_PATH") @classmethod def get_scheme(cls) -> str: @@ -113,6 +134,7 @@ def bucket_url(cls): else: return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" + class FeaturesConfig: enable_custom_cloud_host = to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) "Allows using a custom host in manifest bucket urls" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index e0e9314c38..3d1e9f9197 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -1,8 +1,8 @@ from typing import Dict, List import src.cvat.api_calls as cvat_api +import src.services.cloud as cloud_service import src.models.cvat as cvat_models -import src.services.cloud.utils as cloud_client import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service from src.chain.escrow import get_escrow_manifest, validate_escrow @@ -23,6 +23,7 @@ prepare_annotation_metafile, ) from src.log import ROOT_LOGGER_NAME +from src.services.cloud.types import BucketAccessInfo from src.utils.assignments import parse_manifest from src.utils.logging import get_function_logger @@ -189,7 +190,7 @@ def retrieve_annotations() -> None: Retrieves and stores completed annotations: 1. Retrieves annotations from projects with "completed" status 2. Postprocesses them - 3. Stores annotations in s3 bucket + 3. Stores annotations in S3/GCS bucket 4. Prepares a webhook to recording oracle """ logger = get_function_logger(module_logger) @@ -277,14 +278,10 @@ def retrieve_annotations() -> None: annotation_files.append(annotation_metafile) - storage_client = cloud_client.S3Client( - StorageConfig.provider_endpoint_url(), - access_key=StorageConfig.access_key, - secret_key=StorageConfig.secret_key, - ) + storage_info = BucketAccessInfo.parse_obj(StorageConfig) + storage_client = cloud_service.make_client(storage_info) existing_storage_files = set( storage_client.list_files( - StorageConfig.data_bucket_name, prefix=compose_results_bucket_filename( project.escrow_address, project.chain_id, @@ -297,7 +294,6 @@ def retrieve_annotations() -> None: continue storage_client.create_file( - StorageConfig.data_bucket_name, compose_results_bucket_filename( project.escrow_address, project.chain_id, diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 1660e748af..b28783c698 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -151,11 +151,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.max_embedded_point_radius_percent = 0.01 self.embedded_point_color = (0, 255, 255) - self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config.bucket_url()) - # TODO: add - # credentials=BucketCredentials() - "Exchange Oracle's private bucket info" - + self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) self.min_class_samples_for_roi_estimation = 50 self.max_discarded_threshold = 0.5 diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index dc956abf75..b55277caa0 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -186,16 +186,11 @@ def _download_task_meta(self): layout = boxes_from_points_task.TaskMetaLayout() serializer = boxes_from_points_task.TaskMetaSerializer() - oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config.bucket_url()) - # TODO: add - # credentials=BucketCredentials() - "Exchange Oracle's private bucket info" - + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) storage_client = make_cloud_client(oracle_data_bucket) roi_filenames = serializer.parse_roi_filenames( storage_client.download_fileobj( - oracle_data_bucket.bucket_name, compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME ), @@ -204,7 +199,6 @@ def _download_task_meta(self): rois = serializer.parse_roi_info( storage_client.download_fileobj( - oracle_data_bucket.bucket_name, compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME ), @@ -213,7 +207,6 @@ def _download_task_meta(self): points_dataset = serializer.parse_points_annotations( storage_client.download_fileobj( - oracle_data_bucket.bucket_name, compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.POINTS_FILENAME ), diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index c2d2b5cd3c..f1e822c0c9 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -27,7 +27,9 @@ def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: ... @abstractmethod - def list_files(self, path: Optional[str] = None, *, bucket: Optional[str] = None) -> List[str]: + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: ... @staticmethod diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py index cb594504fa..d76be0353c 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py @@ -2,33 +2,25 @@ # # SPDX-License-Identifier: MIT -import json -import os - -# from google.cloud.exceptions import Forbidden as GoogleCloudForbidden -# from google.cloud.exceptions import NotFound as GoogleCloudNotFound from io import BytesIO -from typing import List, Optional, Union, Dict +from typing import Dict, List, Optional from urllib.parse import unquote from google.cloud import storage from src.services.cloud.client import StorageClient -# TODO: handle cases when bucket/file does not exist class GCSClient(StorageClient): def __init__( self, *, bucket: Optional[str] = None, - service_account_json: Optional[Union[str, Dict]] = None, + service_account_key: Optional[Dict] = None, ) -> None: super().__init__(bucket) - if service_account_json: - if isinstance(service_account_json, str): - service_account_json = json.loads(service_account_json) - self.client = storage.Client.from_service_account_info(service_account_json) + if service_account_key: + self.client = storage.Client.from_service_account_info(service_account_key) else: self.client = storage.Client.create_anonymous_client() @@ -60,7 +52,6 @@ def list_files( self, *, bucket: Optional[str] = None, prefix: Optional[str] = None ) -> List[str]: bucket = unquote(bucket) if bucket else self._bucket - # TODO: performance? prefix = self.normalize_prefix(prefix) return [ diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py index bc141b81ce..65c6752b60 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 CVAT.ai Corporation +# Copyright (C) 2023-2024 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -18,20 +18,20 @@ class S3Client(StorageClient): def __init__( self, - endpoint_url: str, *, bucket: Optional[str] = None, access_key: Optional[str] = None, secret_key: Optional[str] = None, + endpoint_url: Optional[str] = None, ) -> None: super().__init__(bucket) - s3 = boto3.resource( - "s3", + session = boto3.Session( **(dict(aws_access_key_id=access_key) if access_key else {}), **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=unquote(endpoint_url), ) - + s3 = session.resource( + "s3", **({"endpoint_url": unquote(endpoint_url)} if endpoint_url else {}) + ) self.resource = s3 self.client = s3.meta.client @@ -67,7 +67,6 @@ def list_files( self, *, bucket: Optional[str] = None, prefix: Optional[str] = None ) -> List[str]: bucket = unquote(bucket) if bucket else self._bucket - # TODO: performance? objects = self.resource.Bucket(bucket).objects if prefix: objects = objects.filter(Prefix=self.normalize_prefix(prefix)) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py index 785299b997..d6eadc6ba9 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -1,11 +1,12 @@ from __future__ import annotations +import json from dataclasses import dataclass from enum import Enum, auto -from typing import Dict, Optional, Union +from typing import Any, Dict, Optional, Type, Union from urllib.parse import urlparse -from src.core.config import Config +from src.core.config import Config, StorageConfig from src.services.cloud.s3 import DEFAULT_S3_HOST from src.utils.enums import BetterEnumMeta from src.utils.net import is_ipv4 @@ -15,11 +16,50 @@ class CloudProvider(Enum, metaclass=BetterEnumMeta): aws = auto() gcs = auto() + @classmethod + def list(cls): + return [x.value for x in cls] + + @classmethod + def from_str(cls, provider: str) -> CloudProvider: + match provider: + case "aws": + return CloudProvider.aws + case "gcs": + return CloudProvider.gcs + case _: + raise ValueError( + f"{provider} is not supported provider. List with supported providers: {cls.list()}" + ) + class BucketCredentials: def to_dict(self) -> Dict: return self.__dict__ + def from_storage_config(config: Type[StorageConfig]) -> Optional[BucketCredentials]: + credentials = None + + if config.access_key and config.secret_key and config.provider != "aws": + raise Exception( + "Wrong storage configuration. The access_key/secret_key pair" + f"cannot be specified with {config.provider} provider" + ) + + if config.key_file_path and config.provider != "gcs": + raise Exception( + "Wrong storage configuration. The key_file_path" + f"cannot be specified with {config.provider} provider" + ) + + if config.access_key and config.secret_key: + credentials = S3BucketCredentials(config.access_key, config.secret_key) + elif config.key_file_path: + with open(config.key_file_path, "rb") as f: + credentials = GCSCredentials(json.load(f)) + + return credentials + @dataclass class GCSCredentials(BucketCredentials): @@ -78,23 +118,27 @@ def from_url(cls, data: str) -> BucketAccessInfo: path=path, ) else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + raise ValueError( + f"{parsed_url.netloc} cloud provider is not supported by CVAT" + ) @classmethod def from_dict(cls, data: Dict) -> BucketAccessInfo: for required_field in ( "provider", "bucket_name", - ): # probably host_url too + ): if required_field not in data: assert False, f"Missed {required_field} param in bucket configuration" - data['provider'] = { - 'aws': CloudProvider.aws, - 'gcs': CloudProvider.gcs, - }[data['provider'].lower()] + data["provider"] = { + "aws": CloudProvider.aws, + "gcs": CloudProvider.gcs, + }[data["provider"].lower()] - if (access_key := data.pop("access_key", None)) and (secret_key := data.pop("secret_key", None)): + if (access_key := data.pop("access_key", None)) and ( + secret_key := data.pop("secret_key", None) + ): data["credentials"] = S3BucketCredentials(access_key, secret_key) elif service_account_key := data.pop("service_account_key", None): @@ -103,8 +147,23 @@ def from_dict(cls, data: Dict) -> BucketAccessInfo: return BucketAccessInfo(**data) @classmethod - def parse_obj(cls, data: Union[Dict, str]) -> BucketAccessInfo: + def from_storage_config(cls, config: Type[StorageConfig]) -> BucketAccessInfo: + credentials = BucketCredentials.from_storage_config(config) + + return BucketAccessInfo( + provider=CloudProvider.from_str(config.provider), + host_url=config.provider_endpoint_url(), + bucket_name=config.data_bucket_name, + credentials=credentials, + ) + + @classmethod + def parse_obj(cls, data: Union[Dict, str, Type[StorageConfig]]) -> BucketAccessInfo: if isinstance(data, Dict): return cls.from_dict(data) + elif isinstance(data, str): + return cls.from_url(data) + elif issubclass(data, StorageConfig): + return cls.from_storage_config(data) - return cls.from_url(data) + raise ValueError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py index ec31417643..bd2b3ad88a 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py @@ -25,18 +25,19 @@ def make_client( match bucket_info.provider: case CloudProvider.aws: - client_kwargs = {} + ClientClass = S3Client + if bucket_info.credentials: client_kwargs["access_key"] = bucket_info.credentials.access_key client_kwargs["secret_key"] = bucket_info.credentials.secret_key - - client = S3Client(bucket_info.host_url, **client_kwargs) + if bucket_info.host_url: + client_kwargs["endpoint_url"] = bucket_info.host_url case CloudProvider.gcs: - if bucket_info.credentials: - client_kwargs["service_account_json"] = bucket_info.credentials.service_account_key + ClientClass = GCSClient - client = GCSClient(**client_kwargs) + if bucket_info.credentials: + client_kwargs["service_account_key"] = bucket_info.credentials.service_account_key case _: - raise Exception("Unsupported cloud provider") + raise ValueError(f"Unsupported cloud provider ({bucket_info.provider}) was provided") - return client + return ClientClass(**client_kwargs) diff --git a/packages/examples/cvat/recording-oracle/src/.env.template b/packages/examples/cvat/recording-oracle/src/.env.template index 4308688299..285ec69caa 100644 --- a/packages/examples/cvat/recording-oracle/src/.env.template +++ b/packages/examples/cvat/recording-oracle/src/.env.template @@ -35,21 +35,25 @@ PROCESS_REPUTATION_ORACLE_WEBHOOKS_CHUNK_SIZE= # Storage +STORAGE_PROVIDER= STORAGE_ENDPOINT_URL= STORAGE_REGION= STORAGE_ACCESS_KEY= STORAGE_SECRET_KEY= STORAGE_RESULTS_BUCKET_NAME= STORAGE_USE_SSL= +STORAGE_KEY_FILE_PATH= # Exchange Oracle Storage +EXCHANGE_ORACLE_STORAGE_PROVIDER= EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL= EXCHANGE_ORACLE_STORAGE_REGION= EXCHANGE_ORACLE_STORAGE_ACCESS_KEY= EXCHANGE_ORACLE_STORAGE_SECRET_KEY= EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME= EXCHANGE_ORACLE_STORAGE_USE_SSL= +EXCHANGE_ORACLE_STORAGE_KEY_FILE_PATH= # Localhost diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 54f9c37aa5..c31616750b 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -1,9 +1,10 @@ # pylint: disable=too-few-public-methods,missing-class-docstring """ Project configuration from env vars """ import os +from typing import ClassVar, Optional -from dotenv import load_dotenv from attrs.converters import to_bool +from dotenv import load_dotenv from src.utils.logging import parse_log_level from src.utils.net import is_ipv4 @@ -45,7 +46,9 @@ class LocalhostConfig: "LOCALHOST_PRIVATE_KEY", "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) - addr = os.environ.get("LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + addr = os.environ.get( + "LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + ) exchange_oracle_url = os.environ.get("LOCALHOST_EXCHANGE_ORACLE_URL") reputation_oracle_url = os.environ.get("LOCALHOST_REPUTATION_ORACLE_URL") @@ -66,55 +69,65 @@ class CronConfig: ) -class StorageConfig: - endpoint_url = os.environ.get("STORAGE_ENDPOINT_URL", "storage.googleapis.com") - region = os.environ.get("STORAGE_REGION", "") - access_key = os.environ.get("STORAGE_ACCESS_KEY", "") - secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - data_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") - secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) - # TODO: GCS key file +class IStorageConfig: + provider: ClassVar[str] + data_bucket_name: ClassVar[str] + secure: ClassVar[bool] + endpoint_url: ClassVar[str] # TODO: probably should be optional + region: ClassVar[Optional[str]] + # AWS S3 specific attributes + access_key: ClassVar[Optional[str]] + secret_key: ClassVar[Optional[str]] + # GCS specific attributes + key_file_path: ClassVar[Optional[str]] @classmethod def get_scheme(cls) -> str: return "https://" if cls.secure else "http://" @classmethod - def provider_endpoint_url(cls): + def provider_endpoint_url(cls) -> str: return f"{cls.get_scheme()}{cls.endpoint_url}" @classmethod - def bucket_url(cls): + def bucket_url(cls) -> str: if is_ipv4(cls.endpoint_url): return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" else: return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" -class ExchangeOracleStorageConfig: - endpoint_url = os.environ.get("EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL", "storage.googleapis.com") - region = os.environ.get("EXCHANGE_ORACLE_STORAGE_REGION", "") - access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY", "") - secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY", "") - data_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") +class StorageConfig(IStorageConfig): + # common attributes + provider = os.environ["STORAGE_PROVIDER"].lower() + endpoint_url = os.environ[ + "STORAGE_ENDPOINT_URL" + ] # TODO: probably should be optional + region = os.environ.get("STORAGE_REGION") + data_bucket_name = os.environ["STORAGE_RESULTS_BUCKET_NAME"] + secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + # AWS S3 specific attributes + access_key = os.environ.get("STORAGE_ACCESS_KEY") + secret_key = os.environ.get("STORAGE_SECRET_KEY") + # GCS specific attributes + key_file_path = os.environ.get("STORAGE_KEY_FILE_PATH") + + +class ExchangeOracleStorageConfig(IStorageConfig): + # common attributes + provider = os.environ["EXCHANGE_ORACLE_STORAGE_PROVIDER"].lower() + endpoint_url = os.environ[ + "EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL" + ] # TODO: probably should be optional + region = os.environ.get("EXCHANGE_ORACLE_STORAGE_REGION") + data_bucket_name = os.environ["EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME"] results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") secure = to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) - # TODO: GCS key file - - @classmethod - def get_scheme(cls) -> str: - return "https://" if cls.secure else "http://" - - @classmethod - def provider_endpoint_url(cls): - return f"{cls.get_scheme()}{cls.endpoint_url}" - - @classmethod - def bucket_url(cls): - if is_ipv4(cls.endpoint_url): - return f"{cls.get_scheme()}{cls.endpoint_url}/{cls.data_bucket_name}/" - else: - return f"{cls.get_scheme()}{cls.data_bucket_name}.{cls.endpoint_url}/" + # AWS S3 specific attributes + access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY") + secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY") + # GCS specific attributes + key_file_path = os.environ.get("EXCHANGE_ORACLE_STORAGE_KEY_FILE_PATH") class FeaturesConfig: diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index a536bb8f3a..cf051e462d 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -200,11 +200,8 @@ def _download_task_meta(self): serializer = boxes_from_points_task.TaskMetaSerializer() oracle_data_bucket = BucketAccessInfo.parse_obj( - Config.exchange_oracle_storage_config.bucket_url() + Config.exchange_oracle_storage_config ) - # TODO: add - # credentials=BucketCredentials() - "Exchange Oracle's private bucket info" storage_client = make_cloud_client(oracle_data_bucket) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index 6bedbd347d..c17bfaddad 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -47,7 +47,7 @@ def __init__( self.logger: Logger = NullLogger() self.data_bucket = BucketAccessInfo.parse_obj( - Config.exchange_oracle_storage_config.bucket_url() + Config.exchange_oracle_storage_config ) self.annotation_meta: Optional[annotation.AnnotationMeta] = None @@ -157,20 +157,14 @@ def _handle_validation_result(self, validation_result: ValidationResult): ) validation_metafile = serialize_validation_meta(validation_result.validation_meta) - storage_client = s3.S3Client( - Config.storage_config.provider_endpoint_url(), - access_key=Config.storage_config.access_key, - secret_key=Config.storage_config.secret_key, - ) + storage_client = make_cloud_client(BucketAccessInfo.parse_obj(Config.storage_config)) # TODO: add encryption storage_client.create_file( - Config.storage_config.data_bucket_name, recor_merged_annotations_path, validation_result.resulting_annotations, ) storage_client.create_file( - Config.storage_config.data_bucket_name, recor_validation_meta_path, validation_metafile, ) diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py index c2d2b5cd3c..f1e822c0c9 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py @@ -27,7 +27,9 @@ def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: ... @abstractmethod - def list_files(self, path: Optional[str] = None, *, bucket: Optional[str] = None) -> List[str]: + def list_files( + self, *, bucket: Optional[str] = None, prefix: Optional[str] = None + ) -> List[str]: ... @staticmethod diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py index 17db6a9346..d76be0353c 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py @@ -2,33 +2,25 @@ # # SPDX-License-Identifier: MIT -import json -import os - -# from google.cloud.exceptions import Forbidden as GoogleCloudForbidden -# from google.cloud.exceptions import NotFound as GoogleCloudNotFound from io import BytesIO -from typing import List, Optional +from typing import Dict, List, Optional from urllib.parse import unquote from google.cloud import storage from src.services.cloud.client import StorageClient -# TODO: handle cases when bucket/file does not exist class GCSClient(StorageClient): def __init__( self, *, bucket: Optional[str] = None, - service_account_json: Optional[str] = None, + service_account_key: Optional[Dict] = None, ) -> None: super().__init__(bucket) - if service_account_json: - if isinstance(service_account_json, str): - service_account_json = json.loads(service_account_json) - self.client = storage.Client.from_service_account_info(service_account_json) + if service_account_key: + self.client = storage.Client.from_service_account_info(service_account_key) else: self.client = storage.Client.create_anonymous_client() @@ -60,7 +52,6 @@ def list_files( self, *, bucket: Optional[str] = None, prefix: Optional[str] = None ) -> List[str]: bucket = unquote(bucket) if bucket else self._bucket - # TODO: performance? prefix = self.normalize_prefix(prefix) return [ diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py index b3510a3cf1..65c6752b60 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 CVAT.ai Corporation +# Copyright (C) 2023-2024 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -18,20 +18,20 @@ class S3Client(StorageClient): def __init__( self, - endpoint_url: str, *, bucket: Optional[str] = None, access_key: Optional[str] = None, secret_key: Optional[str] = None, + endpoint_url: Optional[str] = None, ) -> None: super().__init__(bucket) - s3 = boto3.resource( - "s3", + session = boto3.Session( **(dict(aws_access_key_id=access_key) if access_key else {}), **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=unquote(endpoint_url), ) - + s3 = session.resource( + "s3", **({"endpoint_url": unquote(endpoint_url)} if endpoint_url else {}) + ) self.resource = s3 self.client = s3.meta.client diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py index b70d561aad..091e0d4578 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -1,11 +1,12 @@ from __future__ import annotations +import json from dataclasses import dataclass from enum import Enum, auto -from typing import Dict, Optional, Union +from typing import Any, Dict, Optional, Type, Union from urllib.parse import urlparse -from src.core.config import Config +from src.core.config import Config, IStorageConfig from src.services.cloud.s3 import DEFAULT_S3_HOST from src.utils.enums import BetterEnumMeta from src.utils.net import is_ipv4 @@ -15,11 +16,51 @@ class CloudProvider(Enum, metaclass=BetterEnumMeta): aws = auto() gcs = auto() + @classmethod + def list(cls): + return [x.value for x in cls] + + @classmethod + def from_str(cls, provider: str) -> CloudProvider: + match provider: + case "aws": + return CloudProvider.aws + case "gcs": + return CloudProvider.gcs + case _: + raise ValueError( + f"{provider} is not supported provider. List with supported providers: {cls.list()}" + ) + class BucketCredentials: def to_dict(self) -> Dict: return self.__dict__ + def from_storage_config(config: Type[IStorageConfig]) -> Optional[BucketCredentials]: + credentials = None + + if config.access_key and config.secret_key and config.provider != "aws": + raise Exception( + "Wrong storage configuration. The access_key/secret_key pair" + f"cannot be specified with {config.provider} provider" + ) + + if config.key_file_path and config.provider != "gcs": + raise Exception( + "Wrong storage configuration. The key_file_path" + f"cannot be specified with {config.provider} provider" + ) + + if config.access_key and config.secret_key: + credentials = S3BucketCredentials(config.access_key, config.secret_key) + elif config.key_file_path: + with open(config.key_file_path, "rb") as f: + credentials = GCSCredentials(json.load(f)) + + return credentials + + @dataclass class GCSCredentials(BucketCredentials): service_account_key: Dict @@ -51,9 +92,8 @@ def from_url(cls, data: str) -> BucketAccessInfo: bucket_name=parsed_url.netloc.split(".")[0], path=parsed_url.path.lstrip("/"), ) - # TODO: elif parsed_url.netloc.endswith("storage.googleapis.com"): - # https://testintegrationwithgcs.storage.googleapis.com + # TODO # Google Cloud Storage (GCS) bucket bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) return BucketAccessInfo( @@ -85,16 +125,18 @@ def from_dict(cls, data: Dict) -> BucketAccessInfo: for required_field in ( "provider", "bucket_name", - ): # probably host_url too + ): if required_field not in data: assert False, f"Missed {required_field} param in bucket configuration" - data['provider'] = { - 'aws': CloudProvider.aws, - 'gcs': CloudProvider.gcs, - }[data['provider'].lower()] + data["provider"] = { + "aws": CloudProvider.aws, + "gcs": CloudProvider.gcs, + }[data["provider"].lower()] - if (access_key := data.pop("access_key", None)) and (secret_key := data.pop("secret_key", None)): + if (access_key := data.pop("access_key", None)) and ( + secret_key := data.pop("secret_key", None) + ): data["credentials"] = S3BucketCredentials(access_key, secret_key) elif service_account_key := data.pop("service_account_key", None): @@ -103,8 +145,23 @@ def from_dict(cls, data: Dict) -> BucketAccessInfo: return BucketAccessInfo(**data) @classmethod - def parse_obj(cls, data: Union[Dict, str]) -> BucketAccessInfo: + def from_storage_config(cls, config: Type[IStorageConfig]) -> BucketAccessInfo: + credentials = BucketCredentials.from_storage_config(config) + + return BucketAccessInfo( + provider=CloudProvider.from_str(config.provider), + host_url=config.provider_endpoint_url(), + bucket_name=config.data_bucket_name, + credentials=credentials, + ) + + @classmethod + def parse_obj(cls, data: Union[Dict, str, Type[IStorageConfig]]) -> BucketAccessInfo: if isinstance(data, Dict): return cls.from_dict(data) + elif isinstance(data, str): + return cls.from_url(data) + elif issubclass(data, IStorageConfig): + return cls.from_storage_config(data) - return cls.from_url(data) + raise ValueError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py index ec31417643..168e79b351 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py @@ -25,18 +25,20 @@ def make_client( match bucket_info.provider: case CloudProvider.aws: - client_kwargs = {} + ClientClass = S3Client if bucket_info.credentials: client_kwargs["access_key"] = bucket_info.credentials.access_key client_kwargs["secret_key"] = bucket_info.credentials.secret_key - client = S3Client(bucket_info.host_url, **client_kwargs) + if bucket_info.host_url: + client_kwargs["endpoint_url"] = bucket_info.host_url + case CloudProvider.gcs: + ClientClass = GCSClient if bucket_info.credentials: - client_kwargs["service_account_json"] = bucket_info.credentials.service_account_key + client_kwargs["service_account_key"] = bucket_info.credentials.service_account_key - client = GCSClient(**client_kwargs) case _: - raise Exception("Unsupported cloud provider") + raise ValueError(f"Unsupported cloud provider ({bucket_info.provider}) was provided") - return client + return ClientClass(**client_kwargs) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index 434d73727b..72427f55c9 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -25,7 +25,7 @@ def tearDown(self): self.client.delete_bucket(Bucket=self.bucket_name) def test_file_operations(self): - client = S3Client(self.url, bucket=self.bucket_name, access_key=self.access_key, secret_key=self.secret) + client = S3Client(endpoint_url=self.url, bucket=self.bucket_name, access_key=self.access_key, secret_key=self.secret) assert len(client.list_files()) == 0 @@ -44,7 +44,7 @@ def test_file_operations(self): assert not client.file_exists(file_name) def test_degenerate_file_operations(self): - client = S3Client(self.url, access_key=self.access_key, secret_key=self.secret) + client = S3Client(endpoint_url=self.url, access_key=self.access_key, secret_key=self.secret) invalid_bucket = "non-existent-bucket" invalid_file = "non-existent-file" @@ -65,9 +65,9 @@ def test_degenerate_file_operations(self): def test_degenerate_client(self): with pytest.raises(EndpointConnectionError): invalid_client = S3Client( - "http://not.an.url:1234", access_key=self.access_key, secret_key=self.secret + endpoint_url="http://not.an.url:1234", access_key=self.access_key, secret_key=self.secret ) invalid_client.create_file("test.txt", bucket=self.bucket_name) with pytest.raises(ValueError): - S3Client("nonsense-stuff") + S3Client(endpoint_url="nonsense-stuff") From 2efc778941412f90ca60d69360d3f537fbf935f3 Mon Sep 17 00:00:00 2001 From: maya Date: Mon, 19 Feb 2024 11:26:49 +0100 Subject: [PATCH 19/82] Add google-cloud-storage dependency --- .../examples/cvat/exchange-oracle/poetry.lock | 274 +++++++++++++++++- .../cvat/exchange-oracle/pyproject.toml | 1 + .../cvat/recording-oracle/poetry.lock | 274 +++++++++++++++++- .../cvat/recording-oracle/pyproject.toml | 1 + 4 files changed, 538 insertions(+), 12 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/poetry.lock b/packages/examples/cvat/exchange-oracle/poetry.lock index 5a13603635..24b1397d99 100644 --- a/packages/examples/cvat/exchange-oracle/poetry.lock +++ b/packages/examples/cvat/exchange-oracle/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -481,6 +481,17 @@ urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] crt = ["awscrt (==0.19.19)"] +[[package]] +name = "cachetools" +version = "5.3.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + [[package]] name = "certifi" version = "2023.11.17" @@ -1424,6 +1435,206 @@ files = [ {file = "future-0.18.3.tar.gz", hash = "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"}, ] +[[package]] +name = "google-api-core" +version = "2.17.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, + {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-auth" +version = "2.28.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.28.0.tar.gz", hash = "sha256:3cfc1b6e4e64797584fb53fc9bd0b7afa9b7c0dba2004fa7dcc9349e58cc3195"}, + {file = "google_auth-2.28.0-py2.py3-none-any.whl", hash = "sha256:7634d29dcd1e101f5226a23cbc4a0c6cda6394253bf80e281d9c5c6797869c53"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-cloud-core" +version = "2.4.1" +description = "Google Cloud API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, +] + +[package.dependencies] +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.25.0,<3.0dev" + +[package.extras] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] + +[[package]] +name = "google-cloud-storage" +version = "2.14.0" +description = "Google Cloud Storage API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-storage-2.14.0.tar.gz", hash = "sha256:2d23fcf59b55e7b45336729c148bb1c464468c69d5efbaee30f7201dd90eb97e"}, + {file = "google_cloud_storage-2.14.0-py2.py3-none-any.whl", hash = "sha256:8641243bbf2a2042c16a6399551fbb13f062cbc9a2de38d6c0bb5426962e9dbd"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=2.23.3,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" +requests = ">=2.18.0,<3.0.0dev" + +[package.extras] +protobuf = ["protobuf (<5.0.0dev)"] + +[[package]] +name = "google-crc32c" +version = "1.5.0" +description = "A python wrapper of the C library 'Google CRC32C'" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, +] + +[package.extras] +testing = ["pytest"] + +[[package]] +name = "google-resumable-media" +version = "2.7.0" +description = "Utilities for Google Media Downloads and Resumable Uploads" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, + {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, +] + +[package.dependencies] +google-crc32c = ">=1.0,<2.0dev" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] +requests = ["requests (>=2.18.0,<3.0.0dev)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.62.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + [[package]] name = "greenlet" version = "3.0.3" @@ -2393,10 +2604,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] [[package]] @@ -2509,9 +2720,9 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -2743,6 +2954,8 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, + {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -2763,6 +2976,20 @@ files = [ {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + [[package]] name = "pycocotools" version = "2.0.7" @@ -3033,6 +3260,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3333,6 +3561,20 @@ files = [ {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -3361,30 +3603,50 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, @@ -4053,4 +4315,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.13" -content-hash = "e35de373388feda7314836a75cf89eec394906188c027b11db1d21d96a623717" +content-hash = "a6a596c9671d180524562680d37c8f0c0fed2d9ae2891854329aedbef30d1aa5" diff --git a/packages/examples/cvat/exchange-oracle/pyproject.toml b/packages/examples/cvat/exchange-oracle/pyproject.toml index ee65c380ef..7581afb8c5 100644 --- a/packages/examples/cvat/exchange-oracle/pyproject.toml +++ b/packages/examples/cvat/exchange-oracle/pyproject.toml @@ -23,6 +23,7 @@ human-protocol-sdk = "^1.1.19" xmltodict = "^0.13.0" datumaro = {git = "https://github.com/cvat-ai/datumaro.git", rev = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11"} boto3 = "^1.28.33" +google-cloud-storage = "^2.14.0" [tool.poetry.group.dev.dependencies] diff --git a/packages/examples/cvat/recording-oracle/poetry.lock b/packages/examples/cvat/recording-oracle/poetry.lock index b67b55a84c..5b4b778e30 100644 --- a/packages/examples/cvat/recording-oracle/poetry.lock +++ b/packages/examples/cvat/recording-oracle/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -481,6 +481,17 @@ urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} [package.extras] crt = ["awscrt (==0.19.19)"] +[[package]] +name = "cachetools" +version = "5.3.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + [[package]] name = "certifi" version = "2023.11.17" @@ -1388,6 +1399,206 @@ files = [ {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] +[[package]] +name = "google-api-core" +version = "2.17.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, + {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-auth" +version = "2.28.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.28.0.tar.gz", hash = "sha256:3cfc1b6e4e64797584fb53fc9bd0b7afa9b7c0dba2004fa7dcc9349e58cc3195"}, + {file = "google_auth-2.28.0-py2.py3-none-any.whl", hash = "sha256:7634d29dcd1e101f5226a23cbc4a0c6cda6394253bf80e281d9c5c6797869c53"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-cloud-core" +version = "2.4.1" +description = "Google Cloud API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, + {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, +] + +[package.dependencies] +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=1.25.0,<3.0dev" + +[package.extras] +grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] + +[[package]] +name = "google-cloud-storage" +version = "2.14.0" +description = "Google Cloud Storage API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-storage-2.14.0.tar.gz", hash = "sha256:2d23fcf59b55e7b45336729c148bb1c464468c69d5efbaee30f7201dd90eb97e"}, + {file = "google_cloud_storage-2.14.0-py2.py3-none-any.whl", hash = "sha256:8641243bbf2a2042c16a6399551fbb13f062cbc9a2de38d6c0bb5426962e9dbd"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=2.23.3,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" +requests = ">=2.18.0,<3.0.0dev" + +[package.extras] +protobuf = ["protobuf (<5.0.0dev)"] + +[[package]] +name = "google-crc32c" +version = "1.5.0" +description = "A python wrapper of the C library 'Google CRC32C'" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, +] + +[package.extras] +testing = ["pytest"] + +[[package]] +name = "google-resumable-media" +version = "2.7.0" +description = "Utilities for Google Media Downloads and Resumable Uploads" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, + {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, +] + +[package.dependencies] +google-crc32c = ">=1.0,<2.0dev" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] +requests = ["requests (>=2.18.0,<3.0.0dev)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.62.0" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, + {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, +] + +[package.dependencies] +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + [[package]] name = "greenlet" version = "3.0.3" @@ -2389,10 +2600,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] [[package]] @@ -2505,9 +2716,9 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -2739,6 +2950,8 @@ files = [ {file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"}, {file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"}, {file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"}, + {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"}, + {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"}, {file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"}, {file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"}, {file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"}, @@ -2759,6 +2972,20 @@ files = [ {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, ] +[[package]] +name = "pyasn1-modules" +version = "0.3.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, + {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.6.0" + [[package]] name = "pycocotools" version = "2.0.7" @@ -3029,6 +3256,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3329,6 +3557,20 @@ files = [ {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + [[package]] name = "ruamel-yaml" version = "0.18.5" @@ -3357,30 +3599,50 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, @@ -3968,4 +4230,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10, <3.13" -content-hash = "ec6d9842af954ffe6ba6e3c67ef593182bb3deb283a0efa994a12b42607fd36f" +content-hash = "45d001b7f9df8a4a2eba103888e31fab0cefb61662d3465cad9836e7dc844fe5" diff --git a/packages/examples/cvat/recording-oracle/pyproject.toml b/packages/examples/cvat/recording-oracle/pyproject.toml index d3402bce81..45acb90a7d 100644 --- a/packages/examples/cvat/recording-oracle/pyproject.toml +++ b/packages/examples/cvat/recording-oracle/pyproject.toml @@ -20,6 +20,7 @@ alembic = "^1.11.1" httpx = "^0.24.1" numpy = "^1.25.2" boto3 = "^1.28.40" +google-cloud-storage = "^2.14.0" datumaro = {git = "https://github.com/cvat-ai/datumaro.git", rev = "ff83c00c2c1bc4b8fdfcc55067fcab0a9b5b6b11"} [tool.poetry.group.dev.dependencies] From 650dd69c0a33037d27d936019a12bc4d995ee65a Mon Sep 17 00:00:00 2001 From: maya Date: Mon, 19 Feb 2024 12:25:39 +0100 Subject: [PATCH 20/82] Fix exception classes --- .../src/services/cloud/types.py | 21 ++++++------- .../src/services/cloud/types.py | 31 +++++++++++-------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py index d6eadc6ba9..9a793c3403 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -3,7 +3,7 @@ import json from dataclasses import dataclass from enum import Enum, auto -from typing import Any, Dict, Optional, Type, Union +from typing import Dict, Optional, Type, Union from urllib.parse import urlparse from src.core.config import Config, StorageConfig @@ -29,7 +29,7 @@ def from_str(cls, provider: str) -> CloudProvider: return CloudProvider.gcs case _: raise ValueError( - f"{provider} is not supported provider. List with supported providers: {cls.list()}" + f"The '{provider}' is not supported provider. List with supported providers: {cls.list()}" ) @@ -41,13 +41,13 @@ def from_storage_config(config: Type[StorageConfig]) -> Optional[BucketCredentia credentials = None if config.access_key and config.secret_key and config.provider != "aws": - raise Exception( + raise ValueError( "Wrong storage configuration. The access_key/secret_key pair" f"cannot be specified with {config.provider} provider" ) if config.key_file_path and config.provider != "gcs": - raise Exception( + raise ValueError( "Wrong storage configuration. The key_file_path" f"cannot be specified with {config.provider} provider" ) @@ -119,7 +119,7 @@ def from_url(cls, data: str) -> BucketAccessInfo: ) else: raise ValueError( - f"{parsed_url.netloc} cloud provider is not supported by CVAT" + f"{parsed_url.netloc} cloud provider is not supported by CVAT." ) @classmethod @@ -129,12 +129,11 @@ def from_dict(cls, data: Dict) -> BucketAccessInfo: "bucket_name", ): if required_field not in data: - assert False, f"Missed {required_field} param in bucket configuration" + raise ValueError( + f"The {required_field} is required and is not specified in the bucket configuration" + ) - data["provider"] = { - "aws": CloudProvider.aws, - "gcs": CloudProvider.gcs, - }[data["provider"].lower()] + data["provider"] = CloudProvider.from_str(data["provider"].lower()) if (access_key := data.pop("access_key", None)) and ( secret_key := data.pop("secret_key", None) @@ -166,4 +165,4 @@ def parse_obj(cls, data: Union[Dict, str, Type[StorageConfig]]) -> BucketAccessI elif issubclass(data, StorageConfig): return cls.from_storage_config(data) - raise ValueError(f"Unsupported data type ({type(data)}) was provided") + raise TypeError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py index 091e0d4578..6d285fdf3c 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -3,7 +3,7 @@ import json from dataclasses import dataclass from enum import Enum, auto -from typing import Any, Dict, Optional, Type, Union +from typing import Dict, Optional, Type, Union from urllib.parse import urlparse from src.core.config import Config, IStorageConfig @@ -29,7 +29,7 @@ def from_str(cls, provider: str) -> CloudProvider: return CloudProvider.gcs case _: raise ValueError( - f"{provider} is not supported provider. List with supported providers: {cls.list()}" + f"The '{provider}' is not supported provider. List with supported providers: {cls.list()}" ) @@ -37,17 +37,19 @@ class BucketCredentials: def to_dict(self) -> Dict: return self.__dict__ - def from_storage_config(config: Type[IStorageConfig]) -> Optional[BucketCredentials]: + def from_storage_config( + config: Type[IStorageConfig], + ) -> Optional[BucketCredentials]: credentials = None if config.access_key and config.secret_key and config.provider != "aws": - raise Exception( + raise ValueError( "Wrong storage configuration. The access_key/secret_key pair" f"cannot be specified with {config.provider} provider" ) if config.key_file_path and config.provider != "gcs": - raise Exception( + raise ValueError( "Wrong storage configuration. The key_file_path" f"cannot be specified with {config.provider} provider" ) @@ -118,7 +120,9 @@ def from_url(cls, data: str) -> BucketAccessInfo: path=path, ) else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + raise ValueError( + f"{parsed_url.netloc} cloud provider is not supported by CVAT" + ) @classmethod def from_dict(cls, data: Dict) -> BucketAccessInfo: @@ -127,12 +131,11 @@ def from_dict(cls, data: Dict) -> BucketAccessInfo: "bucket_name", ): if required_field not in data: - assert False, f"Missed {required_field} param in bucket configuration" + raise ValueError( + f"The {required_field} is required and is not specified in the bucket configuration" + ) - data["provider"] = { - "aws": CloudProvider.aws, - "gcs": CloudProvider.gcs, - }[data["provider"].lower()] + data["provider"] = CloudProvider.from_str(data["provider"].lower()) if (access_key := data.pop("access_key", None)) and ( secret_key := data.pop("secret_key", None) @@ -156,7 +159,9 @@ def from_storage_config(cls, config: Type[IStorageConfig]) -> BucketAccessInfo: ) @classmethod - def parse_obj(cls, data: Union[Dict, str, Type[IStorageConfig]]) -> BucketAccessInfo: + def parse_obj( + cls, data: Union[Dict, str, Type[IStorageConfig]] + ) -> BucketAccessInfo: if isinstance(data, Dict): return cls.from_dict(data) elif isinstance(data, str): @@ -164,4 +169,4 @@ def parse_obj(cls, data: Union[Dict, str, Type[IStorageConfig]]) -> BucketAccess elif issubclass(data, IStorageConfig): return cls.from_storage_config(data) - raise ValueError(f"Unsupported data type ({type(data)}) was provided") + raise TypeError(f"Unsupported data type ({type(data)}) was provided") From 37551a9a58f052554a1e341150627e6a1cea0bcf Mon Sep 17 00:00:00 2001 From: maya Date: Mon, 19 Feb 2024 13:50:24 +0100 Subject: [PATCH 21/82] Set env variables in docker-compose.test.yml --- .../exchange-oracle/docker-compose.test.yml | 1 + .../recording-oracle/docker-compose.test.yml | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/docker-compose.test.yml b/packages/examples/cvat/exchange-oracle/docker-compose.test.yml index cc45d1bfd7..8e02741e6e 100644 --- a/packages/examples/cvat/exchange-oracle/docker-compose.test.yml +++ b/packages/examples/cvat/exchange-oracle/docker-compose.test.yml @@ -70,6 +70,7 @@ services: STORAGE_SECRET_KEY: 'devdevdev' STORAGE_RESULTS_BUCKET_NAME: 'results' STORAGE_USE_SSL: False + STORAGE_PROVIDER: 'aws' ENABLE_CUSTOM_CLOUD_HOST: Yes depends_on: postgres: diff --git a/packages/examples/cvat/recording-oracle/docker-compose.test.yml b/packages/examples/cvat/recording-oracle/docker-compose.test.yml index 3623ae94b9..553cf7957c 100644 --- a/packages/examples/cvat/recording-oracle/docker-compose.test.yml +++ b/packages/examples/cvat/recording-oracle/docker-compose.test.yml @@ -80,11 +80,18 @@ services: PG_PASSWORD: 'test' PG_DB: 'recording_oracle_test' WEB3_HTTP_PROVIDER_URI: 'http://blockchain-node:8545' - ENDPOINT_URL: 'host.docker.internal:9000' - ACCESS_KEY: 'dev' - SECRET_KEY: 'devdevdev' - RESULTS_BUCKET_NAME: 'results' - USE_SSL: False + STORAGE_ENDPOINT_URL: 'host.docker.internal:9000' + STORAGE_ACCESS_KEY: 'dev' + STORAGE_SECRET_KEY: 'devdevdev' + STORAGE_RESULTS_BUCKET_NAME: 'results' + STORAGE_PROVIDER: 'aws' + STORAGE_USE_SSL: False + EXCHANGE_ORACLE_STORAGE_ENDPOINT_URL: 'host.docker.internal:9000' + EXCHANGE_ORACLE_STORAGE_ACCESS_KEY: 'dev' + EXCHANGE_ORACLE_STORAGE_SECRET_KEY: 'devdevdev' + EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME: 'results' + EXCHANGE_ORACLE_STORAGE_USE_SSL: False + EXCHANGE_ORACLE_STORAGE_PROVIDER: 'aws' depends_on: postgres: condition: service_started From 52e347139730c2126f3100f365bea4815e1275c9 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 20 Feb 2024 14:42:00 +0300 Subject: [PATCH 22/82] [CVAT] Points to boxes task (#1560) * Fix job event handling when no assignments exist * Implement boxes from points task creation * Refactor cloud storage api * Implement job downloading * Estimate max bbox * Little refactoring * Copy some shared code from excor to recor * Refactor task creation and export * Implement boxes from points validation * Integrate SDK updates * Fix escrow access * Remove local dev mocks * Remove some extra changes * Update tests * Update some tests * Remove extra launcher change --- .../cvat/exchange-oracle/src/chain/escrow.py | 2 +- .../src/core/annotation_meta.py | 2 +- .../cvat/exchange-oracle/src/core/config.py | 7 +- .../cvat/exchange-oracle/src/core/manifest.py | 5 + .../cvat/exchange-oracle/src/core/storage.py | 10 + .../src/core/tasks/__init__.py | 0 .../src/core/tasks/boxes_from_points.py | 92 ++ .../cvat/exchange-oracle/src/core/types.py | 6 +- .../crons/process_job_launcher_webhooks.py | 2 +- .../src/crons/state_trackers.py | 21 +- .../cvat/exchange-oracle/src/cvat/tasks.py | 210 ---- .../src/handlers/annotation.py | 217 ---- .../src/handlers/cvat_events.py | 2 +- .../src/handlers/job_creation.py | 1108 +++++++++++++++++ .../src/handlers/job_export.py | 299 +++++ .../src/services/cloud/__init__.py | 16 +- .../src/services/cloud/client.py | 65 +- .../exchange-oracle/src/services/cloud/s3.py | 88 ++ .../src/services/cloud/types.py | 44 + .../src/services/cloud/utils.py | 96 ++ .../exchange-oracle/src/utils/annotations.py | 160 +++ .../exchange-oracle/src/utils/assignments.py | 7 - .../src/utils/cloud_storage.py | 64 - .../cvat/exchange-oracle/src/utils/logging.py | 6 + .../tests/api/test_webhook_api.py | 4 +- .../tests/integration/chain/test_escrow.py | 34 +- .../tests/integration/chain/test_kvstore.py | 32 +- .../test_retrieve_annotations.py | 49 +- .../state_trackers/test_track_assignments.py | 2 +- .../test_process_job_launcher_webhooks.py | 55 +- .../test_process_recording_oracle_webhooks.py | 30 +- .../exchange-oracle/tests/utils/constants.py | 3 +- .../cvat/recording-oracle/src/chain/escrow.py | 2 +- .../src/core/annotation_meta.py | 2 +- .../cvat/recording-oracle/src/core/config.py | 13 +- .../cvat/recording-oracle/src/core/storage.py | 10 + .../src/core/tasks/__init__.py | 0 .../src/core/tasks/boxes_from_points.py | 92 ++ .../cvat/recording-oracle/src/core/types.py | 6 +- .../crons/process_exchange_oracle_webhooks.py | 155 +-- .../process_reputation_oracle_webhooks.py | 7 +- .../handlers/process_intermediate_results.py | 345 ++++- .../src/handlers/validation.py | 235 ++++ .../src/services/cloud/__init__.py | 16 +- .../src/services/cloud/client.py | 65 +- .../recording-oracle/src/services/cloud/s3.py | 88 ++ .../src/services/cloud/types.py | 44 + .../src/services/cloud/utils.py | 96 ++ .../recording-oracle/src/utils/annotations.py | 39 + .../src/utils/cloud_storage.py | 64 - .../recording-oracle/src/utils/logging.py | 6 + .../recording-oracle/src/utils/storage.py | 5 - .../src/validation/dataset_comparison.py | 6 +- .../tests/integration/chain/test_escrow.py | 8 +- .../tests/integration/chain/test_kvstore.py | 10 +- .../services/cloud/test_client_service.py | 8 +- .../recording-oracle/tests/utils/constants.py | 3 +- .../tests/utils/setup_escrow.py | 6 +- 58 files changed, 3011 insertions(+), 1058 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/core/storage.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py delete mode 100644 packages/examples/cvat/exchange-oracle/src/cvat/tasks.py delete mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/annotation.py create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/job_export.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/types.py create mode 100644 packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py create mode 100644 packages/examples/cvat/exchange-oracle/src/utils/annotations.py delete mode 100644 packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/storage.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py create mode 100644 packages/examples/cvat/recording-oracle/src/handlers/validation.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/s3.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/types.py create mode 100644 packages/examples/cvat/recording-oracle/src/services/cloud/utils.py create mode 100644 packages/examples/cvat/recording-oracle/src/utils/annotations.py delete mode 100644 packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py delete mode 100644 packages/examples/cvat/recording-oracle/src/utils/storage.py diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index ec32964476..f81b2103a5 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -5,7 +5,7 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient -from src.utils.cloud_storage import parse_bucket_url +from src.services.cloud.utils import parse_bucket_url def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: diff --git a/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py b/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py index 1337c5756e..e19a77efd5 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py +++ b/packages/examples/cvat/exchange-oracle/src/core/annotation_meta.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -ANNOTATION_METAFILE_NAME = "annotation_meta.json" +ANNOTATION_RESULTS_METAFILE_NAME = "annotation_meta.json" RESULTING_ANNOTATIONS_FILE = "resulting_annotations.zip" diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index ae22cbedaa..e0b600a2da 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -98,7 +98,8 @@ class StorageConfig: region = os.environ.get("STORAGE_REGION", "") access_key = os.environ.get("STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") + data_bucket_name = os.environ.get("STORAGE_DATA_BUCKET_NAME", "") + results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) @classmethod @@ -112,9 +113,9 @@ def bucket_url(cls): scheme = "https://" if cls.secure else "http://" if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 390a60c5a6..6d6c293b09 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,4 +1,5 @@ from decimal import Decimal +from typing import Optional from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -11,6 +12,10 @@ class DataInfo(BaseModel): "Bucket URL, s3 only, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + points_url: Optional[AnyUrl] = None + "A path to an archive with a set of points in COCO Keypoints format, " + "which provides information about all objects on images" + class LabelInfo(BaseModel): name: str diff --git a/packages/examples/cvat/exchange-oracle/src/core/storage.py b/packages/examples/cvat/exchange-oracle/src/core/storage.py new file mode 100644 index 0000000000..b934b865c0 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/storage.py @@ -0,0 +1,10 @@ +from src.core.config import Config +from src.core.types import Networks + + +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py new file mode 100644 index 0000000000..c9320473b6 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/boxes_from_points.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +BboxPointMapping = Dict[int, int] + + +@frozen +class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return (Path(gt_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_bbox_point_mapping(self, bbox_point_mapping: BboxPointMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping(self, bbox_point_mapping_data: bytes) -> BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} diff --git a/packages/examples/cvat/exchange-oracle/src/core/types.py b/packages/examples/cvat/exchange-oracle/src/core/types.py index d7d151c7b9..116e0dedf6 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/types.py +++ b/packages/examples/cvat/exchange-oracle/src/core/types.py @@ -40,6 +40,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" + image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): @@ -48,11 +49,6 @@ class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): rectangle = "rectangle" -class CloudProviders(str, Enum, metaclass=BetterEnumMeta): - aws = "AWS_S3_BUCKET" - gcs = "GOOGLE_CLOUD_STORAGE" - - class OracleWebhookTypes(str, Enum, metaclass=BetterEnumMeta): exchange_oracle = "exchange_oracle" job_launcher = "job_launcher" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py index c90e789d27..ecde495883 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py @@ -3,7 +3,7 @@ import httpx from human_protocol_sdk.constants import Status as EscrowStatus -import src.cvat.tasks as cvat +import src.handlers.job_creation as cvat import src.services.cvat as cvat_db_service import src.services.webhook as oracle_db_service from src.chain.escrow import validate_escrow diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 8b5d138d0e..b79ed8fbdb 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -2,7 +2,7 @@ import src.cvat.api_calls as cvat_api import src.models.cvat as cvat_models -import src.services.cloud.client as cloud_client +import src.services.cloud.utils as cloud_client import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service from src.chain.escrow import get_escrow_manifest, validate_escrow @@ -12,17 +12,18 @@ ExchangeOracleEvent_TaskCreationFailed, ExchangeOracleEvent_TaskFinished, ) +from src.core.storage import compose_results_bucket_filename from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatus from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.annotation import ( +from src.handlers.job_export import ( CVAT_EXPORT_FORMAT_MAPPING, FileDescriptor, postprocess_annotations, prepare_annotation_metafile, ) from src.log import ROOT_LOGGER_NAME -from src.utils.assignments import compose_output_annotation_filename, parse_manifest +from src.utils.assignments import parse_manifest from src.utils.logging import get_function_logger module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" @@ -266,8 +267,10 @@ def retrieve_annotations() -> None: ) annotation_files.extend(job_annotations.values()) postprocess_annotations( - annotation_files, - project_annotations_file_desc, + escrow_address=project.escrow_address, + chain_id=project.chain_id, + annotations=annotation_files, + merged_annotation=project_annotations_file_desc, manifest=manifest, project_images=cvat_service.get_project_images(session, project.cvat_id), ) @@ -282,8 +285,8 @@ def retrieve_annotations() -> None: existing_storage_files = set( f.key for f in storage_client.list_files( - StorageConfig.results_bucket_name, - path=compose_output_annotation_filename( + StorageConfig.data_bucket_name, + prefix=compose_results_bucket_filename( project.escrow_address, project.chain_id, "", @@ -295,8 +298,8 @@ def retrieve_annotations() -> None: continue storage_client.create_file( - StorageConfig.results_bucket_name, - compose_output_annotation_filename( + StorageConfig.data_bucket_name, + compose_results_bucket_filename( project.escrow_address, project.chain_id, file_descriptor.filename, diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py b/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py deleted file mode 100644 index c36e088155..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/cvat/tasks.py +++ /dev/null @@ -1,210 +0,0 @@ -import os -import random -from tempfile import TemporaryDirectory -from typing import List - -import datumaro as dm -from datumaro.util import take_by -from datumaro.util.image import IMAGE_EXTENSIONS - -import src.cvat.api_calls as cvat_api -import src.services.cloud as cloud_service -import src.services.cvat as db_service -from src.chain.escrow import get_escrow_manifest -from src.core.manifest import TaskManifest -from src.core.types import CvatLabelType, TaskStatus, TaskType -from src.db import SessionLocal -from src.utils.assignments import parse_manifest -from src.utils.cloud_storage import compose_bucket_url, parse_bucket_url - -LABEL_TYPE_MAPPING = { - TaskType.image_label_binary: CvatLabelType.tag, - TaskType.image_points: CvatLabelType.points, - TaskType.image_boxes: CvatLabelType.rectangle, -} - -DM_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_person_keypoints", - TaskType.image_boxes: "coco_instances", -} - -DM_GT_DATASET_FORMAT_MAPPING = { - # GT uses the same format both for boxes and points - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_instances", - TaskType.image_boxes: "coco_instances", -} - - -def get_gt_filenames( - gt_file_data: bytes, data_filenames: List[str], *, manifest: TaskManifest -) -> List[str]: - with TemporaryDirectory() as gt_temp_dir: - gt_filename = os.path.join(gt_temp_dir, "gt_annotations.json") - with open(gt_filename, "wb") as f: - f.write(gt_file_data) - - gt_dataset = dm.Dataset.import_from( - gt_filename, - format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], - ) - - gt_filenames = set(s.id + s.media.ext for s in gt_dataset) - - known_data_filenames = set(data_filenames) - matched_gt_filenames = gt_filenames.intersection(known_data_filenames) - - if len(gt_filenames) != len(matched_gt_filenames): - missing_gt = gt_filenames - matched_gt_filenames - missing_gt_display_threshold = 10 - remainder = len(missing_gt) - missing_gt_display_threshold - raise Exception( - "Failed to find several validation samples in the dataset files: {}{}".format( - ", ".join(missing_gt[:missing_gt_display_threshold]), - f"(and {remainder} more)" if remainder else "", - ) - ) - - if len(gt_filenames) < manifest.validation.val_size: - raise Exception( - f"Too few validation samples provided ({len(gt_filenames)}), " - f"at least {manifest.validation.val_size} required." - ) - - return matched_gt_filenames - - -def make_job_configuration( - data_filenames: List[str], - gt_filenames: List[str], - *, - manifest: TaskManifest, -) -> List[List[str]]: - # Make job layouts wrt. manifest params, 1 job per task (CVAT can't repeat images in jobs) - gt_filenames_index = set(gt_filenames) - data_filenames = [fn for fn in data_filenames if not fn in gt_filenames_index] - random.shuffle(data_filenames) - - job_layout = [] - for data_samples in take_by(data_filenames, manifest.annotation.job_size): - gt_samples = random.sample(gt_filenames, k=manifest.validation.val_size) - job_samples = list(data_samples) + list(gt_samples) - random.shuffle(job_samples) - job_layout.append(job_samples) - - return job_layout - - -def is_image(path: str) -> bool: - trunk, ext = os.path.splitext(os.path.basename(path)) - return trunk and ext.lower() in IMAGE_EXTENSIONS - - -def filter_image_files(data_filenames: List[str]) -> List[str]: - return list(fn for fn in data_filenames if is_image(fn)) - - -def make_label_configuration(manifest: TaskManifest) -> List[dict]: - return [ - { - "name": label.name, - "type": LABEL_TYPE_MAPPING.get(manifest.annotation.type).value, - } - for label in manifest.annotation.labels - ] - - -def create_task(escrow_address: str, chain_id: int) -> None: - manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) - - parsed_data_bucket_url = parse_bucket_url(manifest.data.data_url) - data_cloud_provider = parsed_data_bucket_url.provider - data_bucket_host = parsed_data_bucket_url.host_url - data_bucket_name = parsed_data_bucket_url.bucket_name - data_bucket_path = parsed_data_bucket_url.path - - # Validate and parse GT - parsed_gt_bucket_url = parse_bucket_url(manifest.validation.gt_url) - gt_bucket_host = parsed_gt_bucket_url.host_url - gt_bucket_name = parsed_gt_bucket_url.bucket_name - gt_filename = parsed_gt_bucket_url.path - - # Register cloud storage on CVAT to pass user dataset - cloud_storage = cvat_api.create_cloudstorage( - data_cloud_provider, data_bucket_host, data_bucket_name - ) - - # Task configuration creation - data_filenames = cloud_service.list_files( - data_bucket_host, - data_bucket_name, - data_bucket_path, - ) - data_filenames = filter_image_files(data_filenames) - - gt_file_data = cloud_service.download_file( - gt_bucket_host, - gt_bucket_name, - gt_filename, - ) - gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) - - job_configuration = make_job_configuration(data_filenames, gt_filenames, manifest=manifest) - label_configuration = make_label_configuration(manifest) - - # Create a project - project = cvat_api.create_project( - escrow_address, - labels=label_configuration, - user_guide=manifest.annotation.user_guide, - ) - - # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) - - with SessionLocal.begin() as session: - db_service.create_project( - session, - project.id, - cloud_storage.id, - manifest.annotation.type, - escrow_address, - chain_id, - compose_bucket_url( - data_bucket_name, - bucket_host=data_bucket_host, - provider=data_cloud_provider, - ), - cvat_webhook_id=webhook.id, - ) - db_service.add_project_images(session, project.id, data_filenames) - - for job_filenames in job_configuration: - task = cvat_api.create_task(project.id, escrow_address) - - with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) - - # Actual task creation in CVAT takes some time, so it's done in an async process. - # The task will be created in DB once 'update:task' or 'update:job' webhook is received. - cvat_api.put_task_data( - task.id, - cloud_storage.id, - filenames=job_filenames, - sort_images=False, - ) - - with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) - - -def remove_task(escrow_address: str) -> None: - with SessionLocal.begin() as session: - project = db_service.get_project_by_escrow_address(session, escrow_address) - if project is not None: - if project.cvat_cloudstorage_id: - cvat_api.delete_cloudstorage(project.cvat_cloudstorage_id) - if project.cvat_id: - cvat_api.delete_project(project.cvat_id) - db_service.delete_project(session, project.id) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py b/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py deleted file mode 100644 index 3913c98ad6..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/handlers/annotation.py +++ /dev/null @@ -1,217 +0,0 @@ -import io -import os -import zipfile -from glob import glob -from tempfile import TemporaryDirectory -from typing import Dict, List, Sequence - -import datumaro as dm -from attrs import define -from defusedxml import ElementTree as ET - -from src.core.annotation_meta import ANNOTATION_METAFILE_NAME, AnnotationMeta, JobMeta -from src.core.manifest import TaskManifest -from src.core.types import TaskType -from src.cvat.tasks import DM_DATASET_FORMAT_MAPPING -from src.models.cvat import Image, Job -from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive - -CVAT_EXPORT_FORMAT_MAPPING = { - TaskType.image_label_binary: "CVAT for images 1.1", - TaskType.image_points: "CVAT for images 1.1", - TaskType.image_boxes: "COCO 1.0", -} - -CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { - "CVAT for images 1.1": "cvat", - "COCO 1.0": "coco_instances", -} - - -@define -class FileDescriptor: - filename: str - file: io.RawIOBase - - -def prepare_annotation_metafile( - jobs: List[Job], job_annotations: Dict[int, FileDescriptor] -) -> FileDescriptor: - """ - Prepares a task/project annotation descriptor file with annotator mapping. - """ - - meta = AnnotationMeta( - jobs=[ - JobMeta( - job_id=job.cvat_id, - annotation_filename=job_annotations[job.cvat_id].filename, - annotator_wallet_address=job.latest_assignment.user_wallet_address, - assignment_id=job.latest_assignment.id, - ) - for job in jobs - ] - ) - - return FileDescriptor(ANNOTATION_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) - - -def flatten_points(input_points: List[dm.Points]) -> List[dm.Points]: - results = [] - - for pts in input_points: - for point_idx in range(len(pts.points) // 2): - point_x = pts.points[2 * point_idx + 0] - point_y = pts.points[2 * point_idx + 1] - results.append(dm.Points([point_x, point_y], label=pts.label)) - - return results - - -def fix_cvat_annotations(dataset_root: str): - for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): - with open(annotation_filename, "rb+") as f: - doc = ET.parse(f) - doc_root = doc.getroot() - - if doc_root.find("meta/project"): - # put labels into each task, if needed - # datumaro doesn't support /meta/project/ tag, but works with tasks, - # which is nested in the meta/project/ - labels_element = doc_root.find("meta/project/labels") - if not labels_element: - continue - - for task_element in doc_root.iterfind("meta/project/tasks/task"): - task_element.append(labels_element) - elif job_meta := doc_root.find("meta/job"): - # just rename the job into task for the same reasons - job_meta.tag = "task" - else: - continue - - f.seek(0) - f.truncate() - doc.write(f, encoding="utf-8") - - -def convert_point_arrays_dataset_to_1_point_skeletons( - dataset: dm.Dataset, labels: List[str] -) -> dm.Dataset: - def _get_skeleton_label(original_label: str) -> str: - return original_label + "_sk" - - new_label_cat = dm.LabelCategories.from_iterable( - [_get_skeleton_label(label) for label in labels] - + [(label, _get_skeleton_label(label)) for label in labels] - ) - new_points_cat = dm.PointsCategories.from_iterable( - (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels - ) - converted_dataset = dm.Dataset( - categories={ - dm.AnnotationType.label: new_label_cat, - dm.AnnotationType.points: new_points_cat, - }, - media_type=dm.Image, - ) - - label_id_map: Dict[int, int] = { - original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] - for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) - } # old id -> new id - - for sample in dataset: - points = [a for a in sample.annotations if isinstance(a, dm.Points)] - points = flatten_points(points) - - skeletons = [ - dm.Skeleton( - [p.wrap(label=label_id_map[p.label])], - label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], - ) - for p in points - ] - - converted_dataset.put(sample.wrap(annotations=skeletons)) - - return converted_dataset - - -def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]): - """ - Removes unknown images from the dataset inplace. - - On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, - with a suffix. We don't need these frames in the resulting dataset, - and we can safely remove them. - """ - if not isinstance(known_frames, set): - known_frames = set(known_frames) - - for sample in list(dataset): - item_image_filename = sample.media.path - - if item_image_filename not in known_frames: - dataset.remove(sample.id, sample.subset) - - -def postprocess_annotations( - annotations: List[FileDescriptor], - merged_annotation: FileDescriptor, - *, - manifest: TaskManifest, - project_images: List[Image], -) -> None: - """ - Processes annotations and updates the files list inplace - """ - - task_type = manifest.annotation.type - - if task_type != TaskType.image_points: - return # CVAT export is fine - - # We need to convert point arrays, which cannot be represented in COCO directly, - # into the 1-point skeletons, compatible with COCO person keypoints, which is the - # required output format - input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[CVAT_EXPORT_FORMAT_MAPPING[task_type]] - resulting_format = DM_DATASET_FORMAT_MAPPING[task_type] - - with TemporaryDirectory() as tempdir: - for ann_descriptor in annotations: - if not zipfile.is_zipfile(ann_descriptor.file): - raise ValueError("Annotation files must be zip files") - ann_descriptor.file.seek(0) - - extract_dir = os.path.join( - tempdir, - os.path.splitext(os.path.basename(ann_descriptor.filename))[0], - ) - extract_zip_archive(ann_descriptor.file, extract_dir) - - fix_cvat_annotations(extract_dir) - dataset = dm.Dataset.import_from(extract_dir, input_format) - - converted_dataset = convert_point_arrays_dataset_to_1_point_skeletons( - dataset, - labels=[label.name for label in manifest.annotation.labels], - ) - - if ann_descriptor.filename == merged_annotation.filename: - remove_duplicated_gt_frames( - converted_dataset, - known_frames=[image.filename for image in project_images], - ) - - export_dir = os.path.join( - tempdir, - os.path.splitext(os.path.basename(ann_descriptor.filename))[0] + "_conv", - ) - converted_dataset.export(export_dir, resulting_format, save_images=False) - - converted_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(export_dir, converted_dataset_archive) - converted_dataset_archive.seek(0) - - ann_descriptor.file = converted_dataset_archive diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py index 1c2f40c290..5f0e8f3851 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py @@ -29,6 +29,7 @@ def handle_update_job_event(payload: dict) -> None: if "state" in payload.before_update: job_assignments = job.assignments + new_status = JobStatuses(payload.job["state"]) if not job_assignments: logger.warning( @@ -36,7 +37,6 @@ def handle_update_job_event(payload: dict) -> None: "No assignments for this job, ignoring the update" ) else: - new_status = JobStatuses(payload.job["state"]) webhook_time = parse_aware_datetime(payload.job["updated_date"]) webhook_assignee_id = (payload.job["assignee"] or {}).get("id") diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py new file mode 100644 index 0000000000..ce8c19179b --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -0,0 +1,1108 @@ +from __future__ import annotations + +import os +import random +import uuid +from contextlib import ExitStack +from itertools import groupby +from logging import Logger +from math import ceil +from tempfile import TemporaryDirectory +from typing import Dict, List, Sequence, Tuple, Union, cast + +import cv2 +import datumaro as dm +import numpy as np +from datumaro.util import take_by +from datumaro.util.image import IMAGE_EXTENSIONS, decode_image, encode_image + +import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.cvat.api_calls as cvat_api +import src.services.cloud as cloud_service +import src.services.cvat as db_service +from src.chain.escrow import get_escrow_manifest +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename +from src.core.types import CvatLabelType, TaskStatus, TaskType +from src.db import SessionLocal +from src.log import ROOT_LOGGER_NAME +from src.services.cloud import CloudProviders, StorageClient +from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url +from src.utils.assignments import parse_manifest +from src.utils.logging import NullLogger, get_function_logger + +module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" + +LABEL_TYPE_MAPPING = { + TaskType.image_label_binary: CvatLabelType.tag, + TaskType.image_points: CvatLabelType.points, + TaskType.image_boxes: CvatLabelType.rectangle, + TaskType.image_boxes_from_points: CvatLabelType.rectangle, +} + +DM_DATASET_FORMAT_MAPPING = { + TaskType.image_label_binary: "cvat_images", + TaskType.image_points: "coco_person_keypoints", + TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", +} + +DM_GT_DATASET_FORMAT_MAPPING = { + # GT uses the same format both for boxes and points + TaskType.image_label_binary: "cvat_images", + TaskType.image_points: "coco_instances", + TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", +} + +CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { + CloudProviders.aws: "AWS_S3_BUCKET", + CloudProviders.gcs: "GOOGLE_CLOUD_STORAGE", +} + + +class DatasetValidationError(Exception): + pass + + +class MismatchingAnnotations(DatasetValidationError): + pass + + +class TooFewSamples(DatasetValidationError): + pass + + +class InvalidCategories(DatasetValidationError): + pass + + +class InvalidImageInfo(DatasetValidationError): + pass + + +class BoxesFromPointsTaskBuilder: + class _NotConfigured: + def __bool__(self) -> bool: + return False + + _not_configured = _NotConfigured() + + def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): + self.exit_stack = ExitStack() + self.manifest = manifest + self.escrow_address = escrow_address + self.chain_id = chain_id + + self.logger: Logger = NullLogger() + + self.input_gt_data: Union[bytes, self._NotConfigured] = self._not_configured + self.input_points_data: Union[bytes, self._NotConfigured] = self._not_configured + + # Computed values + self.input_filenames: Union[self._NotConfigured, Sequence[str]] = self._not_configured + self.input_gt_dataset: Union[self._NotConfigured, dm.Dataset] = self._not_configured + self.input_points_dataset: Union[self._NotConfigured, dm.Dataset] = self._not_configured + + # Output values + self.gt_dataset: Union[dm.Dataset, self._NotConfigured] = self._not_configured + + self.bbox_point_mapping: Union[ + boxes_from_points_task.BboxPointMapping, self._NotConfigured + ] = self._not_configured + "bbox_id -> point_id" + + self.roi_size_estimations: Union[ + Dict[int, Tuple[float, float]], self._NotConfigured + ] = self._not_configured + "label_id -> (rel. w, rel. h)" + + self.rois: Union[ + boxes_from_points_task.RoiInfos, self._NotConfigured + ] = self._not_configured + self.roi_filenames: Union[ + boxes_from_points_task.RoiFilenames, self._NotConfigured + ] = self._not_configured + + self.job_layout: Union[Sequence[Sequence[str]], self._NotConfigured] = self._not_configured + "File lists per CVAT job" + + self.label_configuration: Union[Sequence[dict], self._NotConfigured] = self._not_configured + + # Configuration / constants + # TODO: consider WebP if produced files are too big + self.roi_file_ext = ".png" # supposed to be lossless and reasonably compressing + "File extension for RoI images, with leading dot (.) included" + + self.sample_error_display_threshold = 5 + "The maximum number of rendered list items in a message" + + self.roi_size_mult = 1.1 + "Additional point ROI size multiplier" + + self.points_format = "coco_person_keypoints" + + self.embed_point_in_roi_image = True + "Put a small point into the extracted RoI images for the original point" + + self.embedded_point_radius = 15 + self.min_embedded_point_radius_percent = 0.005 + self.max_embedded_point_radius_percent = 0.01 + self.embedded_point_color = (0, 255, 255) + + self.oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + self.min_class_samples_for_roi_estimation = 50 + + self.max_discarded_threshold = 0.5 + """ + The maximum allowed percent of discarded + GT boxes, points, or samples for successful job launch + """ + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + self.close() + + def close(self): + self.exit_stack.close() + + def set_logger(self, logger: Logger): + # TODO: add escrow info into messages + self.logger = logger + return self + + def _download_input_data(self): + data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + points_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.points_url) + + data_storage_client = self._make_cloud_storage_client(data_bucket) + gt_storage_client = self._make_cloud_storage_client(gt_bucket) + points_storage_client = self._make_cloud_storage_client(points_bucket) + + data_filenames = data_storage_client.list_filenames( + data_bucket.url.bucket_name, + prefix=data_bucket.url.path, + ) + self.input_filenames = filter_image_files(data_filenames) + + self.input_gt_data = gt_storage_client.download_file( + gt_bucket.url.bucket_name, + gt_bucket.url.path, + ) + + self.input_points_data = points_storage_client.download_file( + points_bucket.url.bucket_name, + points_bucket.url.path, + ) + + def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: + temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) + + annotation_filename = os.path.join(temp_dir, "annotations.json") + with open(annotation_filename, "wb") as f: + f.write(annotation_file_data) + + return dm.Dataset.import_from(annotation_filename, format=dataset_format) + + def _parse_gt(self): + assert self.input_gt_data is not self._not_configured + + self.input_gt_dataset = self._parse_dataset( + self.input_gt_data, + dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], + ) + + def _parse_points(self): + assert self.input_points_data is not self._not_configured + + self.input_points_dataset = self._parse_dataset( + self.input_points_data, dataset_format=self.points_format + ) + + def _validate_gt_labels(self): + gt_labels = set( + label.name + for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if gt_labels - manifest_labels: + raise DatasetValidationError( + "GT labels do not match job labels. Unknown labels: {}".format( + self._format_list(gt_labels - manifest_labels), + ) + ) + + self.input_gt_dataset.transform( + "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self.input_gt_dataset.init_cache() + + def _validate_gt_filenames(self): + gt_filenames = set(s.id + s.media.ext for s in self.input_gt_dataset) + + known_data_filenames = set(self.input_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + extra_gt = list(map(os.path.basename, gt_filenames - matched_gt_filenames)) + + raise MismatchingAnnotations( + "Failed to find several validation samples in the dataset files: {}".format( + self._format_list(extra_gt) + ) + ) + + if len(gt_filenames) < self.manifest.validation.val_size: + raise TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {self.manifest.validation.val_size} required." + ) + + def _validate_gt(self): + assert self.input_filenames is not self._not_configured + assert self.input_gt_dataset is not self._not_configured + + self._validate_gt_filenames() + self._validate_gt_labels() + + def _format_list( + self, items: Sequence[str], *, max_items: int = None, separator: str = ", " + ) -> str: + if max_items is None: + max_items = self.sample_error_display_threshold + + remainder_count = len(items) - max_items + return "{}{}".format( + separator.join(items[:max_items]), + f"(and {remainder_count} more)" if remainder_count > 0 else "", + ) + + def _validate_points_categories(self): + invalid_point_categories_messages = [] + points_dataset_categories = self.input_points_dataset.categories() + points_dataset_label_cat: dm.LabelCategories = points_dataset_categories[ + dm.AnnotationType.label + ] + for category_id, category in points_dataset_categories[ + dm.AnnotationType.points + ].items.items(): + if len(category.labels) != 1: + invalid_point_categories_messages.append( + "Category '{}' (#{}): {}".format( + points_dataset_label_cat[category_id].name, + category_id, + f"too many skeleton points ({len(category.labels)}), only 1 expected", + ) + ) + + if invalid_point_categories_messages: + raise InvalidCategories( + "Invalid categories in the input point annotations: {}".format( + self._format_list(invalid_point_categories_messages, separator="; ") + ) + ) + + points_labels = set(label.name for label in points_dataset_label_cat if not label.parent) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if manifest_labels != points_labels: + raise DatasetValidationError("Point labels do not match job labels") + + self.input_points_dataset.transform( + "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self.input_points_dataset.init_cache() + + def _validate_points_filenames(self): + points_filenames = set() + filenames_with_invalid_points = set() + for sample in self.input_points_dataset: + sample_id = sample.id + sample.media.ext + points_filenames.add(sample_id) + + skeletons = [a for a in sample.annotations if isinstance(a, dm.Skeleton)] + for skeleton in skeletons: + if len(skeleton.elements) != 1: + filenames_with_invalid_points.add(sample_id) + break + + if filenames_with_invalid_points: + raise MismatchingAnnotations( + "Some images have invalid points: {}".format( + self._format_list(filenames_with_invalid_points) + ) + ) + + known_data_filenames = set(self.input_filenames) + matched_points_filenames = points_filenames.intersection(known_data_filenames) + + if len(known_data_filenames) != len(matched_points_filenames): + missing_point_samples = list( + map(os.path.basename, known_data_filenames - matched_points_filenames) + ) + extra_point_samples = list( + map(os.path.basename, points_filenames - matched_points_filenames) + ) + + raise MismatchingAnnotations( + "Mismatching points info and input files: {}".format( + "; ".join( + "{} missing points".format(self._format_list(missing_point_samples)), + "{} extra points".format(self._format_list(extra_point_samples)), + ) + ) + ) + + def _validate_points_annotations(self): + label_cat: dm.LabelCategories = self.input_points_dataset.categories()[ + dm.AnnotationType.label + ] + + excluded_samples = [] + for sample in self.input_points_dataset: + image_h, image_w = sample.image.size + + for skeleton in sample.annotations: + # Could fail on this as well + if not isinstance(skeleton, dm.Skeleton): + continue + + point = skeleton.elements[0] + px, py = point.points[:2] + + if px < 0 or py < 0 or px > image_w or py > image_h: + message = ( + "Sample '{}': point #{} ({}) skipped - " + "coordinates are outside image".format( + sample.id, skeleton.id, label_cat[skeleton.label].name + ) + ) + excluded_samples.append(((sample.id, sample.subset), message)) + + if len(excluded_samples) > len(self.input_points_dataset) * self.max_discarded_threshold: + raise DatasetValidationError( + "Too many samples discarded, canceling job creation. Errors: {}".format( + self._format_list([message for _, message in excluded_samples]) + ) + ) + + for excluded_sample, _ in excluded_samples: + self.input_points_dataset.remove(*excluded_sample) + + if excluded_samples: + self.logger.info( + "Some samples were excluded due to errors found: {}".format( + self._format_list([m for _, m in excluded_samples], separator="\n") + ) + ) + + def _validate_points(self): + assert self.input_filenames is not self._not_configured + assert self.input_points_dataset is not self._not_configured + + self._validate_points_categories() + self._validate_points_filenames() + self._validate_points_annotations() + + @staticmethod + def _is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: + return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) + + def _prepare_gt(self): + assert self.input_filenames is not self._not_configured + assert self.input_points_dataset is not self._not_configured + assert self.input_gt_dataset is not self._not_configured + assert [ + label.name for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + ] == [label.name for label in self.manifest.annotation.labels] + assert [ + label.name + for label in self.input_points_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + gt_dataset = dm.Dataset(categories=self.input_gt_dataset.categories(), media_type=dm.Image) + + gt_label_cat: dm.LabelCategories = self.input_gt_dataset.categories()[ + dm.AnnotationType.label + ] + + excluded_boxes_messages = [] + total_boxes = 0 + gt_per_class = {} + + bbox_point_mapping = {} # bbox id -> point id + for gt_sample in self.input_gt_dataset: + points_sample = self.input_points_dataset.get(gt_sample.id, gt_sample.subset) + assert points_sample + + image_h, image_w = points_sample.image.size + + gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] + input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] + + # Samples without boxes are allowed + if not gt_boxes: + continue + + total_boxes += len(gt_boxes) + + matched_boxes = [] + visited_skeletons = set() + for gt_bbox in gt_boxes: + gt_bbox_id = gt_bbox.id + + if ( + gt_bbox.x < 0 + or gt_bbox.y < 0 + or gt_bbox.x + gt_bbox.w > image_w + or gt_bbox.y + gt_bbox.h > image_h + ): + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) - " + "coordinates are outside image. The image will be skipped".format( + gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name + ) + ) + matched_boxes = [] + break + + if len(visited_skeletons) == len(gt_boxes): + # Handle unmatched boxes + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) skipped - " + "no matching points found".format( + gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name + ) + ) + continue + + matched_skeletons: List[dm.Skeleton] = [] + for input_skeleton in input_skeletons: + skeleton_id = input_skeleton.id + if skeleton_id in visited_skeletons: + continue + + input_point = input_skeleton.elements[0] + if not self._is_point_in_bbox(*input_point.points[0:2], bbox=gt_bbox): + continue + + if input_skeleton.label != gt_bbox.label: + continue + + matched_skeletons.append(input_skeleton) + visited_skeletons.add(skeleton_id) + + if len(matched_skeletons) > 1: + # Handle ambiguous matches + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) skipped - " + "too many matching points ({}) found".format( + gt_sample.id, + gt_bbox_id, + gt_label_cat[gt_bbox.label].name, + len(matched_skeletons), + ) + ) + continue + elif len(matched_skeletons) == 0: + # Handle unmatched boxes + excluded_boxes_messages.append( + "Sample '{}': GT bbox #{} ({}) skipped - " + "no matching points found".format( + gt_sample.id, + gt_bbox_id, + gt_label_cat[gt_bbox.label].name, + ) + ) + continue + + matched_boxes.append(gt_bbox) + bbox_point_mapping[gt_bbox_id] = matched_skeletons[0].id + + if not matched_boxes: + continue + + gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) + + if len(bbox_point_mapping) < (1 - self.max_discarded_threshold) * total_boxes: + raise DatasetValidationError( + "Too many GT boxes discarded ({} out of {}). " + "Please make sure each GT box matches exactly 1 point".format( + total_boxes - len(bbox_point_mapping), total_boxes + ) + ) + elif excluded_boxes_messages: + self.logger.info(self._format_list(excluded_boxes_messages, separator="\n")) + + gt_labels_without_anns = [ + gt_label_cat[label_id] + for label_id, label_count in gt_per_class.items() + if not label_count + ] + if gt_labels_without_anns: + raise DatasetValidationError( + "No matching GT boxes/points annotations found for some classes: {}".format( + self._format_list(gt_labels_without_anns) + ) + ) + + self.gt_dataset = gt_dataset + self.bbox_point_mapping = bbox_point_mapping + + def _estimate_roi_sizes(self): + assert self.gt_dataset is not self._not_configured + assert [label.name for label in self.gt_dataset.categories()[dm.AnnotationType.label]] == [ + label.name for label in self.manifest.annotation.labels + ] + + bbox_sizes_per_label = {} + for sample in self.gt_dataset: + image_h, image_w = self.input_points_dataset.get(sample.id, sample.subset).image.size + + for gt_bbox in sample.annotations: + gt_bbox = cast(dm.Bbox, gt_bbox) + bbox_sizes_per_label.setdefault(gt_bbox.label, []).append( + ( + gt_bbox.w / image_w, + gt_bbox.h / image_h, + ) + ) + + # Consider bbox sides as normally-distributed random variables, estimate max + # For big enough datasets, it should be reasonable approximation + # (due to the central limit theorem). This can work bad for small datasets, + # so we only do this if there are enough class samples. + classes_with_default_roi = [] + roi_size_estimations_per_label = {} # label id -> (w, h) + for label_id, label_sizes in bbox_sizes_per_label.items(): + if len(label_sizes) < self.min_class_samples_for_roi_estimation: + classes_with_default_roi.append(label_id) + estimated_size = (2, 2) # 2 will yield just the image size after halving + else: + max_bbox = np.max(label_sizes, axis=0) + estimated_size = max_bbox * self.roi_size_mult + + roi_size_estimations_per_label[label_id] = estimated_size + + if classes_with_default_roi: + label_cat = self.gt_dataset.categories()[dm.AnnotationType.label] + self.logger.debug( + "Some classes will use the full image instead of RoI" + "- too few GT provided: {}".format( + self._format_list( + [label_cat[label_id].name for label_id in classes_with_default_roi] + ) + ) + ) + + self.roi_size_estimations = roi_size_estimations_per_label + + def _prepare_roi_info(self): + assert self.gt_dataset is not self._not_configured + assert self.roi_size_estimations is not self._not_configured + assert self.input_points_dataset is not self._not_configured + + rois: List[boxes_from_points_task.RoiInfo] = [] + for sample in self.input_points_dataset: + for skeleton in sample.annotations: + if not isinstance(skeleton, dm.Skeleton): + continue + + point_label_id = skeleton.label + original_point_x, original_point_y = skeleton.elements[0].points[:2] + original_point_x = int(original_point_x) + original_point_y = int(original_point_y) + + image_h, image_w = sample.image.size + + roi_est_w, roi_est_h = self.roi_size_estimations[point_label_id] + roi_est_w *= image_w + roi_est_h *= image_h + + roi_left = max(0, original_point_x - int(roi_est_w / 2)) + roi_top = max(0, original_point_y - int(roi_est_h / 2)) + roi_right = min(image_w, original_point_x + ceil(roi_est_w / 2)) + roi_bottom = min(image_h, original_point_y + ceil(roi_est_h / 2)) + + roi_w = roi_right - roi_left + roi_h = roi_bottom - roi_top + + new_point_x = original_point_x - roi_left + new_point_y = original_point_y - roi_top + + rois.append( + boxes_from_points_task.RoiInfo( + point_id=skeleton.id, + original_image_key=sample.attributes["id"], + point_x=new_point_x, + point_y=new_point_y, + roi_x=roi_left, + roi_y=roi_top, + roi_w=roi_w, + roi_h=roi_h, + ) + ) + + self.rois = rois + + def _mangle_filenames(self): + """ + Mangle filenames in the dataset to make them less recognizable by annotators + and hide private dataset info + """ + assert self.rois is not self._not_configured + + # TODO: maybe add different names for the same GT images in + # different jobs to make them even less recognizable + self.roi_filenames = { + roi.point_id: str(uuid.uuid4()) + self.roi_file_ext for roi in self.rois + } + + def _prepare_job_layout(self): + # Make job layouts wrt. manifest params + # 1 job per task as CVAT can't repeat images in jobs, but GTs can repeat in the dataset + + assert self.rois is not self._not_configured + assert self.bbox_point_mapping is not self._not_configured + + gt_point_ids = set(self.bbox_point_mapping.values()) + gt_filenames = [self.roi_filenames[point_id] for point_id in gt_point_ids] + + data_filenames = [ + fn for point_id, fn in self.roi_filenames.items() if not point_id in gt_point_ids + ] + random.shuffle(data_filenames) + + job_layout = [] + for data_samples in take_by(data_filenames, self.manifest.annotation.job_size): + gt_samples = random.sample(gt_filenames, k=self.manifest.validation.val_size) + job_samples = list(data_samples) + list(gt_samples) + random.shuffle(job_samples) + job_layout.append(job_samples) + + self.job_layout = job_layout + + def _prepare_label_configuration(self): + self.label_configuration = make_label_configuration(self.manifest) + + def _upload_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + file_list = [] + file_list.append((self.input_points_data, layout.POINTS_FILENAME)) + file_list.append( + ( + serializer.serialize_gt_annotations(self.gt_dataset), + layout.GT_FILENAME, + ) + ) + file_list.append( + ( + serializer.serialize_bbox_point_mapping(self.bbox_point_mapping), + layout.BBOX_POINT_MAPPING_FILENAME, + ) + ) + file_list.append((serializer.serialize_roi_info(self.rois), layout.ROI_INFO_FILENAME)) + file_list.append( + (serializer.serialize_roi_filenames(self.roi_filenames), layout.ROI_FILENAMES_FILENAME) + ) + + storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) + bucket_name = self.oracle_data_bucket.url.bucket_name + for file_data, filename in file_list: + storage_client.create_file( + bucket_name, + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + file_data, + ) + + def _draw_roi_point( + self, roi_pixels: np.ndarray, roi_info: boxes_from_points_task.RoiInfo + ) -> np.ndarray: + center = (roi_info.point_x, roi_info.point_y) + + roi_r = (roi_info.roi_w**2 + roi_info.roi_h**2) ** 0.5 / 2 + point_size = int( + min( + self.max_embedded_point_radius_percent * roi_r, + max(self.embedded_point_radius, self.min_embedded_point_radius_percent * roi_r), + ) + ) + + roi_pixels = roi_pixels.copy() + roi_pixels = cv2.circle( + roi_pixels, + center, + point_size + 1, + (255, 255, 255), + cv2.FILLED, + ) + roi_pixels = cv2.circle( + roi_pixels, + center, + point_size, + self.embedded_point_color, + cv2.FILLED, + ) + + return roi_pixels + + def _extract_and_upload_rois(self): + # TODO: maybe optimize via splitting into separate threads (downloading, uploading, processing) + # Watch for the memory used, as the whole dataset can be quite big (gigabytes, terabytes) + # Consider also packing RoIs cut into archives + assert self.input_points_dataset is not self._not_configured + assert self.rois is not self._not_configured + assert self.input_filenames is not self._not_configured + assert self.roi_filenames is not self._not_configured + + src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + src_prefix = "" + dst_bucket = self.oracle_data_bucket + + src_client = self._make_cloud_storage_client(src_bucket) + dst_client = self._make_cloud_storage_client(dst_bucket) + + image_id_to_filename = { + sample.attributes["id"]: sample.image.path for sample in self.input_points_dataset + } + + filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} + + _roi_key = lambda e: e.original_image_key + rois_by_image: Dict[str, Sequence[boxes_from_points_task.RoiInfo]] = { + image_id_to_filename[image_id]: list(g) + for image_id, g in groupby(sorted(self.rois, key=_roi_key), key=_roi_key) + } + + for filename in self.input_filenames: + image_roi_infos = rois_by_image.get(filename, []) + if not image_roi_infos: + continue + + image_bytes = src_client.download_file( + src_bucket.url.bucket_name, os.path.join(src_prefix, filename) + ) + image_pixels = decode_image(image_bytes) + + sample = filename_to_sample[filename] + if tuple(sample.image.size) != tuple(image_pixels.shape[:2]): + # TODO: maybe rois should be regenerated instead + # Option 2: accumulate errors, fail when some threshold is reached + # Option 3: add special handling for cases when image is only rotated (exif etc.) + raise InvalidImageInfo( + f"Sample '{filename}': invalid size provided in the point annotations" + ) + + image_rois = {} + for roi_info in image_roi_infos: + roi_pixels = image_pixels[ + roi_info.roi_y : roi_info.roi_y + roi_info.roi_h, + roi_info.roi_x : roi_info.roi_x + roi_info.roi_w, + ] + + if self.embed_point_in_roi_image: + roi_pixels = self._draw_roi_point(roi_pixels, roi_info) + + roi_filename = self.roi_filenames[roi_info.point_id] + roi_bytes = encode_image(roi_pixels, os.path.splitext(roi_filename)[-1]) + + image_rois[roi_filename] = roi_bytes + + for roi_filename, roi_bytes in image_rois.items(): + dst_client.create_file( + dst_bucket.url.bucket_name, + compose_data_bucket_filename(self.escrow_address, self.chain_id, roi_filename), + roi_bytes, + ) + + def _create_on_cvat(self): + assert self.job_layout is not self._not_configured + assert self.label_configuration is not self._not_configured + + input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + oracle_bucket = self.oracle_data_bucket + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[oracle_bucket.provider], + oracle_bucket.url.host_url, + oracle_bucket.url.bucket_name, + # TODO: add + # credentials=... + ) + + # Create a project + project = cvat_api.create_project( + self.escrow_address, + labels=self.label_configuration, + user_guide=self.manifest.annotation.user_guide, + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + input_data_bucket = parse_bucket_url(self.manifest.data.data_url) + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.bucket_name, + bucket_host=input_data_bucket.host_url, + provider=input_data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images( + session, + project.id, + [ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in self.roi_filenames.values() + ], + ) + + for job_filenames in self.job_layout: + task = cvat_api.create_task(project.id, self.escrow_address) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=[ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in job_filenames + ], + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + @classmethod + def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: + return cloud_service.make_client(bucket_info) + + def build(self): + self._download_input_data() + self._parse_gt() + self._parse_points() + self._validate_gt() + self._validate_points() + + # Task configuration creation + self._prepare_gt() + self._estimate_roi_sizes() + self._prepare_roi_info() + self._mangle_filenames() + self._prepare_label_configuration() + self._prepare_job_layout() + + # Data preparation + self._extract_and_upload_rois() + self._upload_task_meta() + + self._create_on_cvat() + + +def get_gt_filenames( + gt_file_data: bytes, data_filenames: List[str], *, manifest: TaskManifest +) -> List[str]: + with TemporaryDirectory() as gt_temp_dir: + gt_filename = os.path.join(gt_temp_dir, "gt_annotations.json") + with open(gt_filename, "wb") as f: + f.write(gt_file_data) + + gt_dataset = dm.Dataset.import_from( + gt_filename, + format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + + gt_filenames = set(s.id + s.media.ext for s in gt_dataset) + + known_data_filenames = set(data_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + missing_gt = gt_filenames - matched_gt_filenames + missing_gt_display_threshold = 10 + remainder = len(missing_gt) - missing_gt_display_threshold + raise DatasetValidationError( + "Failed to find several validation samples in the dataset files: {}{}".format( + ", ".join(missing_gt[:missing_gt_display_threshold]), + f"(and {remainder} more)" if remainder else "", + ) + ) + + if len(gt_filenames) < manifest.validation.val_size: + raise TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {manifest.validation.val_size} required." + ) + + return matched_gt_filenames + + +def make_job_configuration( + data_filenames: List[str], + gt_filenames: List[str], + *, + manifest: TaskManifest, +) -> List[List[str]]: + # Make job layouts wrt. manifest params, 1 job per task (CVAT can't repeat images in jobs) + gt_filenames_index = set(gt_filenames) + data_filenames = [fn for fn in data_filenames if not fn in gt_filenames_index] + random.shuffle(data_filenames) + + job_layout = [] + for data_samples in take_by(data_filenames, manifest.annotation.job_size): + gt_samples = random.sample(gt_filenames, k=manifest.validation.val_size) + job_samples = list(data_samples) + list(gt_samples) + random.shuffle(job_samples) + job_layout.append(job_samples) + + return job_layout + + +def is_image(path: str) -> bool: + trunk, ext = os.path.splitext(os.path.basename(path)) + return trunk and ext.lower() in IMAGE_EXTENSIONS + + +def filter_image_files(data_filenames: List[str]) -> List[str]: + return list(fn for fn in data_filenames if is_image(fn)) + + +def make_label_configuration(manifest: TaskManifest) -> List[dict]: + return [ + { + "name": label.name, + "type": LABEL_TYPE_MAPPING[manifest.annotation.type].value, + } + for label in manifest.annotation.labels + ] + + +def create_task(escrow_address: str, chain_id: int) -> None: + logger = get_function_logger(module_logger) + + manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) + + if manifest.annotation.type in [ + TaskType.image_boxes, + TaskType.image_points, + TaskType.image_label_binary, + ]: + data_bucket = parse_bucket_url(manifest.data.data_url) + gt_bucket = parse_bucket_url(manifest.validation.gt_url) + + data_bucket_client = cloud_service.make_client(data_bucket) + gt_bucket_client = cloud_service.make_client(gt_bucket) + + # Task configuration creation + data_filenames = data_bucket_client.list_filenames( + data_bucket.bucket_name, + prefix=data_bucket.path, + ) + data_filenames = filter_image_files(data_filenames) + + gt_file_data = gt_bucket_client.download_file( + gt_bucket.bucket_name, + gt_bucket.path, + ) + + # Validate and parse GT + gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) + + job_configuration = make_job_configuration(data_filenames, gt_filenames, manifest=manifest) + label_configuration = make_label_configuration(manifest) + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[data_bucket.provider], + data_bucket.host_url, + data_bucket.bucket_name, + ) + + # Create a project + project = cvat_api.create_project( + escrow_address, + labels=label_configuration, + user_guide=manifest.annotation.user_guide, + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + manifest.annotation.type, + escrow_address, + chain_id, + compose_bucket_url( + data_bucket.bucket_name, + bucket_host=data_bucket.host_url, + provider=data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images(session, project.id, data_filenames) + + for job_filenames in job_configuration: + task = cvat_api.create_task(project.id, escrow_address) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=job_filenames, + sort_images=False, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + elif manifest.annotation.type in [TaskType.image_boxes_from_points]: + with BoxesFromPointsTaskBuilder(manifest, escrow_address, chain_id) as task_builder: + task_builder.set_logger(logger) + task_builder.build() + + else: + raise Exception(f"Unsupported task type {manifest.annotation.type}") + + +def remove_task(escrow_address: str) -> None: + with SessionLocal.begin() as session: + project = db_service.get_project_by_escrow_address(session, escrow_address) + if project is not None: + if project.cvat_cloudstorage_id: + cvat_api.delete_cloudstorage(project.cvat_cloudstorage_id) + if project.cvat_id: + cvat_api.delete_project(project.cvat_id) + db_service.delete_project(session, project.id) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py new file mode 100644 index 0000000000..ac6966908f --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -0,0 +1,299 @@ +import io +import os +import zipfile +from tempfile import TemporaryDirectory +from typing import Dict, List, Type + +import datumaro as dm +from attrs import define +from datumaro.components.dataset import Dataset + +import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.utils.annotations as annotation_utils +from src.core.annotation_meta import ANNOTATION_RESULTS_METAFILE_NAME, AnnotationMeta, JobMeta +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename +from src.core.types import TaskType +from src.handlers.job_creation import DM_DATASET_FORMAT_MAPPING +from src.models.cvat import Image, Job +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud.utils import BucketAccessInfo +from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive + +CVAT_EXPORT_FORMAT_MAPPING = { + TaskType.image_label_binary: "CVAT for images 1.1", + TaskType.image_points: "CVAT for images 1.1", + TaskType.image_boxes: "COCO 1.0", + TaskType.image_boxes_from_points: "COCO 1.0", +} + +CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { + "CVAT for images 1.1": "cvat", + "COCO 1.0": "coco_instances", +} + + +@define +class FileDescriptor: + filename: str + file: io.RawIOBase + + +def prepare_annotation_metafile( + jobs: List[Job], job_annotations: Dict[int, FileDescriptor] +) -> FileDescriptor: + """ + Prepares a task/project annotation descriptor file with annotator mapping. + """ + + meta = AnnotationMeta( + jobs=[ + JobMeta( + job_id=job.cvat_id, + annotation_filename=job_annotations[job.cvat_id].filename, + annotator_wallet_address=job.latest_assignment.user_wallet_address, + assignment_id=job.latest_assignment.id, + ) + for job in jobs + ] + ) + + return FileDescriptor(ANNOTATION_RESULTS_METAFILE_NAME, file=io.BytesIO(meta.json().encode())) + + +class _TaskProcessor: + def __init__( + self, + escrow_address: str, + chain_id: int, + annotations: List[FileDescriptor], + merged_annotation: FileDescriptor, + *, + manifest: TaskManifest, + project_images: List[Image], + ): + self.escrow_address = escrow_address + self.chain_id = chain_id + self.annotations = annotations + self.merged_annotation = merged_annotation + self.manifest = manifest + self.project_images = project_images + + self.input_format = CVAT_EXPORT_FORMAT_TO_DM_MAPPING[ + CVAT_EXPORT_FORMAT_MAPPING[manifest.annotation.type] + ] + self.output_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + + def process(self): + with TemporaryDirectory() as tempdir: + for ann_descriptor in self.annotations: + if not zipfile.is_zipfile(ann_descriptor.file): + raise ValueError("Annotation files must be zip files") + ann_descriptor.file.seek(0) + + extract_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(ann_descriptor.filename))[0], + ) + extract_zip_archive(ann_descriptor.file, extract_dir) + + export_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(ann_descriptor.filename))[0] + "_conv", + ) + + self._process_annotation_file(ann_descriptor, extract_dir, export_dir) + + converted_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(export_dir, converted_dataset_archive) + converted_dataset_archive.seek(0) + + ann_descriptor.file = converted_dataset_archive + + def _process_annotation_file( + self, ann_descriptor: FileDescriptor, input_dir: str, output_dir: str + ): + input_dataset = self._parse_dataset(ann_descriptor, input_dir) + output_dataset = self._process_dataset(input_dataset, ann_descriptor=ann_descriptor) + self._export_dataset(output_dataset, output_dir) + + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> dm.Dataset: + return dm.Dataset.import_from(dataset_dir, self.input_format) + + def _export_dataset(self, dataset: dm.Dataset, output_dir: str): + dataset.export(output_dir, self.output_format, save_images=False) + + def _process_dataset( + self, dataset: dm.Dataset, *, ann_descriptor: FileDescriptor + ) -> dm.Dataset: + # TODO: remove complete duplicates in annotations + + if ann_descriptor.filename == self.merged_annotation.filename: + dataset = self._process_merged_dataset(dataset) + + return dataset + + def _process_merged_dataset(self, input_dataset: dm.Dataset) -> dm.Dataset: + return annotation_utils.remove_duplicated_gt_frames( + input_dataset, + known_frames=[image.filename for image in self.project_images], + ) + + +class _LabelsTaskProcessor(_TaskProcessor): + pass + + +class _BoxesTaskProcessor(_TaskProcessor): + pass + + +class _PointsTaskProcessor(_TaskProcessor): + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> Dataset: + annotation_utils.prepare_cvat_annotations_for_dm(dataset_dir) + return super()._parse_dataset(ann_descriptor, dataset_dir) + + def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) -> Dataset: + # We need to convert point arrays, which cannot be represented in COCO directly, + # into the 1-point skeletons, compatible with COCO person keypoints, which is the + # required output format + dataset = annotation_utils.convert_point_arrays_dataset_to_1_point_skeletons( + dataset, + labels=[label.name for label in self.manifest.annotation.labels], + ) + + return super()._process_dataset(dataset, ann_descriptor=ann_descriptor) + + +class _BoxesFromPointsTaskProcessor(_TaskProcessor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + roi_filenames, roi_infos, points_dataset = self._download_task_meta() + + self.points_dataset = points_dataset + self.original_key_to_sample = {sample.attributes["id"]: sample for sample in points_dataset} + + roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + + self.roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + def _download_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + storage_client = make_cloud_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + points_dataset = serializer.parse_points_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINTS_FILENAME + ), + ) + ) + + return roi_filenames, rois, points_dataset + + def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: + point_roi_dataset = super()._process_merged_dataset(input_dataset) + + merged_sample_dataset = dm.Dataset( + media_type=dm.Image, + categories={ + dm.AnnotationType.label: dm.LabelCategories.from_iterable( + [label.name for label in self.manifest.annotation.labels] + ) + }, + ) + + for roi_sample in point_roi_dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(roi_sample.id)] + original_sample = self.original_key_to_sample[roi_info.original_image_key] + + merged_sample = merged_sample_dataset.get(original_sample.id) + if not merged_sample: + merged_sample = original_sample.wrap(annotations=[]) + merged_sample_dataset.put(merged_sample) + + image_h, image_w = merged_sample.image.size + + old_point = next( + skeleton + for skeleton in original_sample.annotations + if skeleton.id == roi_info.point_id + if isinstance(skeleton, dm.Skeleton) + ).elements[0] + old_x, old_y = old_point.points[:2] + + merged_sample.annotations.extend( + annotation_utils.shift_ann( + roi_ann, + offset_x=old_x - roi_info.point_x, + offset_y=old_y - roi_info.point_y, + img_w=image_w, + img_h=image_h, + ) + for roi_ann in roi_sample.annotations + if isinstance(roi_ann, dm.Bbox) + ) + + return merged_sample_dataset + + +def postprocess_annotations( + escrow_address: str, + chain_id: int, + annotations: List[FileDescriptor], + merged_annotation: FileDescriptor, + *, + manifest: TaskManifest, + project_images: List[Image], +) -> None: + """ + Processes annotations and updates the files list inplace + """ + processor_classes: Dict[TaskType, Type[_TaskProcessor]] = { + TaskType.image_label_binary: _LabelsTaskProcessor, + TaskType.image_boxes: _BoxesTaskProcessor, + TaskType.image_points: _PointsTaskProcessor, + TaskType.image_boxes_from_points: _BoxesFromPointsTaskProcessor, + } + + task_type = manifest.annotation.type + processor = processor_classes[task_type]( + escrow_address=escrow_address, + chain_id=chain_id, + annotations=annotations, + merged_annotation=merged_annotation, + manifest=manifest, + project_images=project_images, + ) + processor.process() diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py index eb128cc100..007394d9ed 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py @@ -1,13 +1,3 @@ -from typing import Optional - -from src.services.cloud.client import S3Client - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_fileobj(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, path: Optional[str] = None) -> list[str]: - client = S3Client(bucket_host) - return [f.key for f in client.list_files(bucket_name, path=path)] +from src.services.cloud.client import StorageClient +from src.services.cloud.types import CloudProviders +from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index 2c6101a573..0a80d1706e 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -1,61 +1,24 @@ -# Copyright (C) 2022 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - -from io import BytesIO +from abc import ABCMeta, abstractmethod from typing import List, Optional -import boto3 -from botocore.exceptions import ClientError -from botocore.handlers import disable_signing - - -class S3Client: - def __init__( - self, - endpoint_url: str, - *, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - ) -> None: - s3 = boto3.resource( - "s3", - **(dict(aws_access_key_id=access_key) if access_key else {}), - **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=endpoint_url, - ) - - self.resource = s3 - self.client = s3.meta.client - - if not access_key and not secret_key: - self.client.meta.events.register("choose-signer.s3.*", disable_signing) +class StorageClient(metaclass=ABCMeta): + @abstractmethod def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=bucket, Key=filename) + ... + @abstractmethod def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=bucket, Key=filename) + ... + @abstractmethod def file_exists(self, bucket: str, filename: str) -> bool: - try: - self.client.head_object(Bucket=bucket, Key=filename) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - else: - raise + ... - def download_fileobj(self, bucket: str, key: str) -> bytes: - with BytesIO() as data: - self.client.download_fileobj(Bucket=bucket, Key=key, Fileobj=data) - return data.getvalue() + @abstractmethod + def download_file(self, bucket: str, key: str) -> bytes: + ... - def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(bucket).objects - if path: - objects = objects.filter(Prefix=path.strip("/\\") + "/") - else: - objects = objects.all() - return list(objects) + @abstractmethod + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + ... diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py new file mode 100644 index 0000000000..ed00f2799e --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from dataclasses import dataclass +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +import boto3 +from botocore.exceptions import ClientError +from botocore.handlers import disable_signing + +from src.services.cloud.client import StorageClient +from src.services.cloud.types import BucketCredentials + + +class S3Client(StorageClient): + def __init__( + self, + endpoint_url: str, + *, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + ) -> None: + s3 = boto3.resource( + "s3", + **(dict(aws_access_key_id=access_key) if access_key else {}), + **(dict(aws_secret_access_key=secret_key) if secret_key else {}), + endpoint_url=unquote(endpoint_url), + ) + + self.resource = s3 + self.client = s3.meta.client + + if not access_key and not secret_key: + self.client.meta.events.register("choose-signer.s3.*", disable_signing) + + def create_file(self, bucket: str, filename: str, data: bytes = b""): + self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + + def remove_file(self, bucket: str, filename: str): + self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + + def file_exists(self, bucket: str, filename: str) -> bool: + try: + self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return False + else: + raise + + def download_file(self, bucket: str, key: str) -> bytes: + with BytesIO() as data: + self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) + return data.getvalue() + + def list_files(self, bucket: str, *, prefix: Optional[str] = None) -> List: + objects = self.resource.Bucket(unquote(bucket)).objects + if prefix: + objects = objects.filter(Prefix=unquote(prefix).strip("/\\") + "/") + else: + objects = objects.all() + return list(objects) + + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + return [file_info.key for file_info in self.list_files(bucket=bucket, prefix=prefix)] + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +DEFAULT_S3_HOST = "s3.amazonaws.com" + + +def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: + client = S3Client(bucket_host) + return client.download_file(bucket_name, filename) + + +def list_files(bucket_host: str, bucket_name: str, *, prefix: Optional[str] = None) -> List[str]: + client = S3Client(bucket_host) + return client.list_filenames(bucket_name, prefix=prefix) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py new file mode 100644 index 0000000000..ee89883d52 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum, auto +from typing import Optional + +from src.utils.enums import BetterEnumMeta + + +class CloudProviders(Enum, metaclass=BetterEnumMeta): + aws = auto() + gcs = auto() + + +@dataclass +class BucketUrl: + provider: CloudProviders + host_url: str + bucket_name: str + path: str + + +class BucketCredentials: + pass + + +@dataclass +class BucketAccessInfo: + url: BucketUrl + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_raw_url(cls, url: str) -> BucketAccessInfo: + from src.services.cloud.utils import parse_bucket_url + + return cls.from_parsed_url(parse_bucket_url(url)) + + @classmethod + def from_parsed_url(cls, parsed_url: BucketUrl) -> BucketAccessInfo: + return BucketAccessInfo(url=parsed_url) + + @property + def provider(self) -> CloudProviders: + return self.url.provider diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py new file mode 100644 index 0000000000..33b9a4f7ee --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py @@ -0,0 +1,96 @@ +from typing import Optional, Union, overload +from urllib.parse import urlparse + +from src.core.config import Config +from src.services.cloud.client import StorageClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, BucketUrl, CloudProviders +from src.utils.net import is_ipv4 + + +def parse_bucket_url(data_url: str) -> BucketUrl: + parsed_url = urlparse(data_url) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketUrl( + provider=CloudProviders.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + # elif parsed_url.netloc.endswith("storage.googleapis.com"): + # # Google Cloud Storage (GCS) bucket + # return ParsedBucketUrl( + # provider=CloudProviders.gcs, + # bucket_name=parsed_url.netloc.split(".")[0], + # ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketUrl( + provider=CloudProviders.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + + +def compose_bucket_url( + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None +) -> str: + match provider: + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" + + +@overload +def make_client(url: BucketUrl, credentials: Optional[BucketCredentials] = None) -> StorageClient: + ... + + +@overload +def make_client( + bucket_info: BucketAccessInfo, +) -> StorageClient: + ... + + +def make_client( + _pos1: Union[BucketUrl, BucketAccessInfo, None] = None, + *, + bucket_info: Optional[BucketAccessInfo] = None, + url: Optional[BucketUrl] = None, + credentials: Optional[BucketCredentials] = None, +) -> StorageClient: + if _pos1 is not None: + if isinstance(_pos1, BucketAccessInfo): + bucket_info = _pos1 + else: + url = _pos1 + + if bucket_info is None: + bucket_info = BucketAccessInfo(url=url, credentials=credentials) + + match bucket_info.provider: + case CloudProviders.aws: + client_kwargs = {} + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + client = S3Client(bucket_info.url.host_url, **client_kwargs) + case _: + raise Exception("Unsupported cloud provider") + + return client diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py new file mode 100644 index 0000000000..4edf05609f --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -0,0 +1,160 @@ +import os +from glob import glob +from typing import Dict, List, Sequence + +import datumaro as dm +import numpy as np +from defusedxml import ElementTree as ET + + +def flatten_points(input_points: Sequence[dm.Points]) -> List[dm.Points]: + results = [] + + for pts in input_points: + for point_idx in range(len(pts.points) // 2): + point_x = pts.points[2 * point_idx + 0] + point_y = pts.points[2 * point_idx + 1] + results.append(dm.Points([point_x, point_y], label=pts.label)) + + return results + + +def prepare_cvat_annotations_for_dm(dataset_root: str): + """ + Fixes project/job annotations from CVAT exported in the CVAT format + to make them readable by Datumaro. + + Datumaro doesn't support the 'meta/project' and 'meta/job' keys, + but does support 'meta/task'. The key formats are the same, + only the name is different. The key is needed to parse labels correctly. + """ + + for annotation_filename in glob(os.path.join(dataset_root, "**/*.xml"), recursive=True): + with open(annotation_filename, "rb+") as f: + doc = ET.parse(f) + doc_root = doc.getroot() + + if doc_root.find("meta/project"): + # put labels into each task, if needed + # datumaro doesn't support /meta/project/ tag, but works with tasks, + # which is nested in the meta/project/ + labels_element = doc_root.find("meta/project/labels") + if not labels_element: + continue + + for task_element in doc_root.iterfind("meta/project/tasks/task"): + task_element.append(labels_element) + elif job_meta := doc_root.find("meta/job"): + # just rename the job into task for the same reasons + job_meta.tag = "task" + else: + continue + + f.seek(0) + f.truncate() + doc.write(f, encoding="utf-8") + + +def convert_point_arrays_dataset_to_1_point_skeletons( + dataset: dm.Dataset, labels: Sequence[str] +) -> dm.Dataset: + """ + In the COCO Person Keypoints format, we can only represent points inside skeletons. + The function converts annotations from points to skeletons in the dataset. + """ + + def _get_skeleton_label(original_label: str) -> str: + return original_label + "_sk" + + new_label_cat = dm.LabelCategories.from_iterable( + [_get_skeleton_label(label) for label in labels] + + [(label, _get_skeleton_label(label)) for label in labels] + ) + new_points_cat = dm.PointsCategories.from_iterable( + (new_label_cat.find(_get_skeleton_label(label))[0], [label]) for label in labels + ) + converted_dataset = dm.Dataset( + categories={ + dm.AnnotationType.label: new_label_cat, + dm.AnnotationType.points: new_points_cat, + }, + media_type=dm.Image, + ) + + label_id_map: Dict[int, int] = { + original_id: new_label_cat.find(label.name, parent=_get_skeleton_label(label.name))[0] + for original_id, label in enumerate(dataset.categories()[dm.AnnotationType.label]) + } # old id -> new id + + for sample in dataset: + points = [a for a in sample.annotations if isinstance(a, dm.Points)] + points = flatten_points(points) + + skeletons = [ + dm.Skeleton( + [p.wrap(label=label_id_map[p.label])], + label=new_label_cat.find(_get_skeleton_label(labels[p.label]))[0], + ) + for p in points + ] + + converted_dataset.put(sample.wrap(annotations=skeletons)) + + return converted_dataset + + +def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str]) -> dm.Dataset: + """ + Removes unknown images from the dataset inplace. + + On project dataset export, CVAT will add GT frames, which repeat in multiple tasks, + with a suffix. We don't need these frames in the resulting dataset, + and we can safely remove them. + """ + if not isinstance(known_frames, set): + known_frames = set(known_frames) + + for sample in list(dataset): + item_image_filename = sample.media.path + + if item_image_filename not in known_frames: + dataset.remove(sample.id, sample.subset) + + return dataset + + +def shift_ann( + ann: dm.Annotation, offset_x: float, offset_y: float, *, img_w: int, img_h: int +) -> dm.Annotation: + "Shift annotation coordinates with clipping to the image size" + + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index 29b3adcc1e..8176203fee 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -2,7 +2,6 @@ from src.core.config import Config from src.core.manifest import TaskManifest -from src.core.types import Networks def parse_manifest(manifest: dict) -> TaskManifest: @@ -11,9 +10,3 @@ def parse_manifest(manifest: dict) -> TaskManifest: def compose_assignment_url(task_id, job_id) -> str: return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}") - - -def compose_output_annotation_filename( - escrow_address: str, chain_id: Networks, filename: str -) -> str: - return f"{escrow_address}@{chain_id}/{filename}" diff --git a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py deleted file mode 100644 index 3418fde5bc..0000000000 --- a/packages/examples/cvat/exchange-oracle/src/utils/cloud_storage.py +++ /dev/null @@ -1,64 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from urllib.parse import urlparse - -from src.core.config import Config -from src.core.types import CloudProviders -from src.utils.net import is_ipv4 - - -@dataclass -class ParsedBucketUrl: - provider: str - host_url: str - bucket_name: str - path: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def parse_bucket_url(data_url: str) -> ParsedBucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs.value, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") - - -def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None -) -> str: - match provider: - case CloudProviders.aws.value: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs.value: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" diff --git a/packages/examples/cvat/exchange-oracle/src/utils/logging.py b/packages/examples/cvat/exchange-oracle/src/utils/logging.py index d9ce28d024..146f6c219b 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/logging.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/logging.py @@ -20,3 +20,9 @@ def get_function_logger( function_name = current_function_name(depth=2) return parent_logger.getChild(function_name) + + +class NullLogger(logging.Logger): + def __init__(self, name: str = "", level=0) -> None: + super().__init__(name, level) + self.disabled = True diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py index 56958c1f32..64164fcf11 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py @@ -23,7 +23,7 @@ def test_incoming_webhook_200(client: TestClient) -> None: mock_get_web3.return_value = Web3(HTTPProvider(Networks.localhost)) mock_escrow = Mock() mock_escrow.launcher = JOB_LAUNCHER - mock_escrow.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow.recording_oracle = RECORDING_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow response = client.post( @@ -130,7 +130,7 @@ def test_incoming_webhook_401(client: TestClient) -> None: mock_get_web3.return_value = Web3(HTTPProvider(Networks.localhost)) mock_escrow = Mock() mock_escrow.launcher = escrow_address - mock_escrow.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow.recording_oracle = RECORDING_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow response = client.post( "/oracle-webhook", diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index 565fd5772e..5aa045f3a3 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -14,7 +14,7 @@ ) from tests.utils.constants import ( - DEFAULT_URL, + DEFAULT_MANIFEST_URL, ESCROW_ADDRESS, FACTORY_ADDRESS, JOB_LAUNCHER_ADDRESS, @@ -41,7 +41,7 @@ def setUp(self): token=TOKEN_ADDRESS, total_funded_amount=1000, created_at="", - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, recording_oracle=RECORDING_ORACLE_ADDRESS, ) @@ -55,9 +55,7 @@ def test_validate_escrow_invalid_address(self): with self.assertRaises(EscrowClientError) as error: validate_escrow(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_validate_escrow_invalid_status(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: @@ -94,9 +92,7 @@ def test_get_escrow_manifest(self): def test_get_escrow_manifest_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_escrow_manifest(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: @@ -108,9 +104,7 @@ def test_get_job_launcher_address(self): def test_get_job_launcher_address_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_job_launcher_address(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_address_invalid_chain_id(self): with self.assertRaises(ValueError) as error: @@ -122,26 +116,20 @@ def test_get_job_launcher_address_empty_escrow(self): mock_function.return_value = None with self.assertRaises(Exception) as error: get_job_launcher_address(chain_id, escrow_address) - self.assertEqual( - f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception) - ) + self.assertEqual(f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception)) def test_get_recording_oracle_address(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_function.return_value = self.escrow_data - recording_oracle_address = get_recording_oracle_address( - chain_id, escrow_address - ) + recording_oracle_address = get_recording_oracle_address(chain_id, escrow_address) self.assertIsInstance(recording_oracle_address, str) self.assertEqual(recording_oracle_address, RECORDING_ORACLE_ADDRESS) def test_get_recording_oracle_address_invalid_address(self): with self.assertRaises(EscrowClientError) as error: get_recording_oracle_address(chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_recording_oracle_address_invalid_chain_id(self): with self.assertRaises(ValueError) as error: @@ -153,6 +141,4 @@ def test_get_recording_oracle_address_empty_escrow(self): mock_function.return_value = None with self.assertRaises(Exception) as error: get_recording_oracle_address(chain_id, escrow_address) - self.assertEqual( - f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception) - ) + self.assertEqual(f"Can't find escrow {ESCROW_ADDRESS}", str(error.exception)) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py index 5c3e2be8b8..3d8b5e0976 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_kvstore.py @@ -8,7 +8,7 @@ from src.chain.kvstore import get_job_launcher_url, get_recording_oracle_url from tests.utils.constants import ( - DEFAULT_URL, + DEFAULT_MANIFEST_URL, ESCROW_ADDRESS, FACTORY_ADDRESS, JOB_LAUNCHER_ADDRESS, @@ -36,7 +36,7 @@ def setUp(self): token=TOKEN_ADDRESS, total_funded_amount=1000, created_at="", - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, recording_oracle=RECORDING_ORACLE_ADDRESS, ) @@ -45,16 +45,14 @@ def test_get_job_launcher_url(self): "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: mock_escrow.return_value = self.escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) recording_url = get_job_launcher_url(self.w3.eth.chain_id, escrow_address) - self.assertEqual(recording_url, DEFAULT_URL) + self.assertEqual(recording_url, DEFAULT_MANIFEST_URL) def test_get_job_launcher_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: get_job_launcher_url(self.w3.eth.chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_job_launcher_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( @@ -69,29 +67,23 @@ def test_get_recording_oracle_url(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) - recording_url = get_recording_oracle_url( - self.w3.eth.chain_id, escrow_address - ) - self.assertEqual(recording_url, DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) + recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) + self.assertEqual(recording_url, DEFAULT_MANIFEST_URL) def test_get_recording_oracle_url_invalid_escrow(self): with self.assertRaises(EscrowClientError) as error: get_recording_oracle_url(self.w3.eth.chain_id, "invalid_address") - self.assertEqual( - f"Invalid escrow address: invalid_address", str(error.exception) - ) + self.assertEqual(f"Invalid escrow address: invalid_address", str(error.exception)) def test_get_recording_oracle_url_invalid_recording_address(self): with patch("src.chain.kvstore.get_escrow") as mock_escrow, patch( "src.chain.kvstore.StakingUtils.get_leader" ) as mock_leader: - self.escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + self.escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = self.escrow_data mock_leader.return_value = MagicMock(webhook_url="") - recording_url = get_recording_oracle_url( - self.w3.eth.chain_id, escrow_address - ) + recording_url = get_recording_oracle_url(self.w3.eth.chain_id, escrow_address) self.assertEqual(recording_url, "") diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py index 600a30a624..bf467e09aa 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py @@ -1,10 +1,16 @@ +import io import json +import os import unittest import uuid +import zipfile from datetime import datetime, timedelta -from io import RawIOBase +from glob import glob +from tempfile import TemporaryDirectory from unittest.mock import Mock, patch +import datumaro as dm + from src.core.types import ( ExchangeOracleEventType, JobStatuses, @@ -15,7 +21,7 @@ ) from src.crons.state_trackers import retrieve_annotations from src.db import SessionLocal -from src.models.cvat import Assignment, Job, Project, Task, User +from src.models.cvat import Assignment, Image, Job, Project, Task, User from src.models.webhook import Webhook @@ -42,6 +48,14 @@ def test_retrieve_annotations(self): ) self.session.add(cvat_project) + project_images = ["sample1.jpg", "sample2.png"] + for image_filename in project_images: + self.session.add( + Image( + id=str(uuid.uuid4()), cvat_project_id=cvat_project_id, filename=image_filename + ) + ) + cvat_task_id = 1 cvat_task = Task( id=str(uuid.uuid4()), @@ -86,14 +100,37 @@ def test_retrieve_annotations(self): with ( open("tests/utils/manifest.json") as data, patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), + patch("src.crons.state_trackers.cvat_api") as mock_cvat_api, patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client.S3Client") as mock_S3Client, + patch("src.crons.state_trackers.cloud_client") as mock_cloud_client, ): manifest = json.load(data) mock_get_manifest.return_value = manifest - mock_create_file = Mock() - mock_S3Client.return_value.create_file = mock_create_file + + dummy_zip_file = io.BytesIO() + with zipfile.ZipFile(dummy_zip_file, "w") as archive, TemporaryDirectory() as tempdir: + mock_dataset = dm.Dataset( + media_type=dm.Image, + categories={ + dm.AnnotationType.label: dm.LabelCategories.from_iterable(["cat", "dog"]) + }, + ) + for image_filename in project_images: + mock_dataset.put(dm.DatasetItem(id=os.path.splitext(image_filename)[0])) + mock_dataset.export(tempdir, format="coco_instances") + + for filename in list(glob(os.path.join(tempdir, "**/*"), recursive=True)): + archive.write(filename, os.path.relpath(filename, tempdir)) + dummy_zip_file.seek(0) + + mock_cvat_api.get_job_annotations.return_value = dummy_zip_file + mock_cvat_api.get_project_annotations.return_value = dummy_zip_file + + mock_storage_client = Mock() + mock_storage_client.create_file = Mock() + mock_storage_client.list_files = Mock(return_value=[]) + mock_cloud_client.make_client = Mock(return_value=mock_storage_client) + mock_cloud_client.S3Client = Mock(return_value=mock_storage_client) retrieve_annotations() diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py index 10a5b8b772..5d339072b1 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py @@ -85,7 +85,7 @@ def test_track_expired_assignments(self): # TODO: # Fix src/crons/state_trackers.py # Where in `cvat_service.get_active_assignments()` return value will be empty - # because it actually looking for the expired assignments + # because it actually looking for the expired assignments # def test_track_canceled_assignments(self): # (_, _, cvat_job) = create_project_task_and_job( diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index f51bf3ddc1..29cc14e4b5 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -26,7 +26,7 @@ from src.models.webhook import Webhook from src.services.webhook import OracleWebhookDirectionTag -from tests.utils.constants import DEFAULT_URL, JOB_LAUNCHER_ADDRESS +from tests.utils.constants import DEFAULT_MANIFEST_URL, JOB_LAUNCHER_ADDRESS escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value @@ -57,10 +57,10 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): with ( patch("src.chain.escrow.get_escrow") as mock_escrow, open("tests/utils/manifest.json") as data, - patch("src.cvat.tasks.get_escrow_manifest") as mock_get_manifest, - patch("src.cvat.tasks.cvat_api") as mock_cvat_api, - patch("src.cvat.tasks.cloud_service") as mock_cloud_service, - patch("src.cvat.tasks.get_gt_filenames") as mock_gt_filenames, + patch("src.handlers.job_creation.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.job_creation.cvat_api") as mock_cvat_api, + patch("src.handlers.job_creation.cloud_service.make_client") as mock_make_cloud_client, + patch("src.handlers.job_creation.get_gt_filenames") as mock_gt_filenames, ): manifest = json.load(data) mock_get_manifest.return_value = manifest @@ -77,14 +77,15 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): "image2.jpg", ] mock_gt_filenames.return_value = filenames - mock_cloud_service.list_files.return_value = filenames + + mock_cloud_client = Mock() + mock_cloud_client.list_filenames.return_value = filenames + mock_make_cloud_client.return_value = mock_cloud_client process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -193,12 +194,12 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ with ( patch("src.chain.escrow.get_escrow") as mock_escrow, open("tests/utils/manifest.json") as data, - patch("src.cvat.tasks.get_escrow_manifest") as mock_get_manifest, - patch("src.cvat.tasks.cvat_api") as mock_cvat_api, - patch("src.cvat.tasks.cloud_service"), - patch("src.cvat.tasks.get_gt_filenames"), + patch("src.handlers.job_creation.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.job_creation.cvat_api") as mock_cvat_api, + patch("src.handlers.job_creation.cloud_service"), + patch("src.handlers.job_creation.get_gt_filenames"), patch( - "src.cvat.tasks.db_service.add_project_images", + "src.handlers.job_creation.db_service.add_project_images", side_effect=Exception("Error"), ), ): @@ -216,9 +217,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -268,9 +267,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -321,9 +318,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -375,9 +370,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal process_incoming_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) @@ -417,7 +410,7 @@ def test_process_outgoing_job_launcher_webhooks(self): mock_escrow_data = Mock() mock_escrow_data.launcher = JOB_LAUNCHER_ADDRESS mock_escrow.return_value = mock_escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) mock_response = MagicMock() mock_response.raise_for_status.return_value = None mock_httpx_post.return_value = mock_response @@ -425,9 +418,7 @@ def test_process_outgoing_job_launcher_webhooks(self): process_outgoing_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -455,9 +446,7 @@ def test_process_outgoing_job_launcher_webhooks_invalid_type(self): process_outgoing_job_launcher_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py index 7ca1cbe360..e7f534cc90 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py @@ -25,7 +25,7 @@ from src.models.webhook import Webhook from src.services.webhook import OracleWebhookDirectionTag -from tests.utils.constants import DEFAULT_URL, RECORDING_ORACLE_ADDRESS +from tests.utils.constants import DEFAULT_MANIFEST_URL, RECORDING_ORACLE_ADDRESS escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value @@ -70,9 +70,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -115,9 +113,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -180,9 +176,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -236,9 +230,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva process_incoming_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -273,9 +265,9 @@ def test_process_outgoing_recording_oracle_webhooks(self): w3 = Mock() w3.eth.chain_id = ChainId.LOCALHOST.value mock_escrow_data = Mock() - mock_escrow_data.recordingOracle = RECORDING_ORACLE_ADDRESS + mock_escrow_data.recording_oracle = RECORDING_ORACLE_ADDRESS mock_escrow.return_value = mock_escrow_data - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) mock_response = MagicMock() mock_response.raise_for_status.return_value = None mock_httpx_post.return_value = mock_response @@ -283,9 +275,7 @@ def test_process_outgoing_recording_oracle_webhooks(self): process_outgoing_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) @@ -313,9 +303,7 @@ def test_process_outgoing_recording_oracle_webhooks_invalid_type(self): process_outgoing_recording_oracle_webhooks() updated_webhook = ( - self.session.execute(select(Webhook).where(Webhook.id == webhok_id)) - .scalars() - .first() + self.session.execute(select(Webhook).where(Webhook.id == webhok_id)).scalars().first() ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.pending.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/constants.py b/packages/examples/cvat/exchange-oracle/tests/utils/constants.py index 33e16d146e..2c3655e57f 100644 --- a/packages/examples/cvat/exchange-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/exchange-oracle/tests/utils/constants.py @@ -5,6 +5,7 @@ RECORDING_ORACLE_PRIV = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" RECORDING_ORACLE_FEE = 10 +REPUTATION_ORACLE_WEBHOOK_URL = "http://localhost:5001/webhook/cvat" REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_FEE = 10 @@ -17,7 +18,7 @@ TOKEN_ADDRESS = "0x976EA74026E726554dB657fA54763abd0C3a0aa9" FACTORY_ADDRESS = "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" -DEFAULT_URL = "http://host.docker.internal:9000/manifests/manifest.json" +DEFAULT_MANIFEST_URL = "http://host.docker.internal:9000/manifests/manifest.json" DEFAULT_HASH = "test" SIGNATURE = "0xa0c5626301e3c198cb91356e492890c0c28db8c37044846134939246911a693c4d7116d04aa4bc40a41077493868b8dd533d30980f6addb28d1b3610a84cb4091c" diff --git a/packages/examples/cvat/recording-oracle/src/chain/escrow.py b/packages/examples/cvat/recording-oracle/src/chain/escrow.py index 416cc10bba..58883fb6d7 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/recording-oracle/src/chain/escrow.py @@ -6,7 +6,7 @@ from human_protocol_sdk.storage import StorageClient from src.chain.web3 import get_web3 -from src.utils.cloud_storage import parse_bucket_url +from src.services.cloud.utils import parse_bucket_url def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: diff --git a/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py b/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py index 1337c5756e..e19a77efd5 100644 --- a/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py +++ b/packages/examples/cvat/recording-oracle/src/core/annotation_meta.py @@ -3,7 +3,7 @@ from pydantic import BaseModel -ANNOTATION_METAFILE_NAME = "annotation_meta.json" +ANNOTATION_RESULTS_METAFILE_NAME = "annotation_meta.json" RESULTING_ANNOTATIONS_FILE = "resulting_annotations.zip" diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 3353b5e1b3..a647973007 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -76,7 +76,7 @@ class StorageConfig: region = os.environ.get("STORAGE_REGION", "") access_key = os.environ.get("STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") + data_bucket_name = os.environ.get("STORAGE_RESULTS_BUCKET_NAME", "") secure = str_to_bool(os.environ.get("STORAGE_USE_SSL", "true")) @classmethod @@ -90,9 +90,9 @@ def bucket_url(cls): scheme = "https://" if cls.secure else "http://" if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" class ExchangeOracleStorageConfig: @@ -100,7 +100,8 @@ class ExchangeOracleStorageConfig: region = os.environ.get("EXCHANGE_ORACLE_STORAGE_REGION", "") access_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_ACCESS_KEY", "") secret_key = os.environ.get("EXCHANGE_ORACLE_STORAGE_SECRET_KEY", "") - results_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") + data_bucket_name = os.environ.get("EXCHANGE_ORACLE_STORAGE_RESULTS_BUCKET_NAME", "") + results_dir_suffix = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") secure = str_to_bool(os.environ.get("EXCHANGE_ORACLE_STORAGE_USE_SSL", "true")) @classmethod @@ -114,9 +115,9 @@ def bucket_url(cls): scheme = "https://" if cls.secure else "http://" if is_ipv4(cls.endpoint_url): - return f"{scheme}{cls.endpoint_url}/{cls.results_bucket_name}/" + return f"{scheme}{cls.endpoint_url}/{cls.data_bucket_name}/" else: - return f"{scheme}{cls.results_bucket_name}.{cls.endpoint_url}/" + return f"{scheme}{cls.data_bucket_name}.{cls.endpoint_url}/" class FeaturesConfig: diff --git a/packages/examples/cvat/recording-oracle/src/core/storage.py b/packages/examples/cvat/recording-oracle/src/core/storage.py new file mode 100644 index 0000000000..6c5c53dbe2 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/storage.py @@ -0,0 +1,10 @@ +from src.core.config import Config +from src.core.types import Networks + + +def compose_data_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}/{filename}" + + +def compose_results_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: + return f"{escrow_address}@{chain_id}{Config.exchange_oracle_storage_config.results_dir_suffix}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py b/packages/examples/cvat/recording-oracle/src/core/tasks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py b/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py new file mode 100644 index 0000000000..c9320473b6 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/tasks/boxes_from_points.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +BboxPointMapping = Dict[int, int] + + +@frozen +class RoiInfo: + point_id: int + original_image_key: int + point_x: int + point_y: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + POINTS_FILENAME = "points.json" + BBOX_POINT_MAPPING_FILENAME = "bbox_point_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_instances" + POINTS_DATASET_FORMAT = "coco_person_keypoints" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return (Path(gt_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_bbox_point_mapping(self, bbox_point_mapping: BboxPointMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in bbox_point_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_points_annotations(self, points_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(points_dataset_data) + + dataset = dm.Dataset.import_from( + annotations_filename, format=self.POINTS_DATASET_FORMAT + ) + dataset.init_cache() + return dataset + + def parse_bbox_point_mapping(self, bbox_point_mapping_data: bytes) -> BboxPointMapping: + return {int(k): int(v) for k, v in parse_json(bbox_point_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} diff --git a/packages/examples/cvat/recording-oracle/src/core/types.py b/packages/examples/cvat/recording-oracle/src/core/types.py index 90ff668752..ab9f3492e3 100644 --- a/packages/examples/cvat/recording-oracle/src/core/types.py +++ b/packages/examples/cvat/recording-oracle/src/core/types.py @@ -14,6 +14,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" + image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" class OracleWebhookTypes(str, Enum): @@ -36,8 +37,3 @@ class ExchangeOracleEventType(str, Enum, metaclass=BetterEnumMeta): class RecordingOracleEventType(str, Enum, metaclass=BetterEnumMeta): task_completed = "task_completed" task_rejected = "task_rejected" - - -class CloudProviders(str, Enum, metaclass=BetterEnumMeta): - aws = "AWS_S3_BUCKET" - gcs = "GOOGLE_CLOUD_STORAGE" diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py index 710001cc8c..aef5ebb812 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py @@ -1,38 +1,19 @@ -import io import logging -import os from typing import Dict import httpx from sqlalchemy.orm import Session -import src.chain.escrow as escrow -import src.core.annotation_meta as annotation -import src.core.validation_meta as validation -import src.services.cloud.client as cloud_client import src.services.webhook as oracle_db_service from src.chain.kvstore import get_exchange_oracle_url from src.core.config import Config -from src.core.oracle_events import ( - RecordingOracleEvent_TaskCompleted, - RecordingOracleEvent_TaskRejected, -) from src.core.types import ExchangeOracleEventType, OracleWebhookTypes from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.process_intermediate_results import ( - ValidationSuccess, - parse_annotation_metafile, - process_intermediate_results, - serialize_validation_meta, -) +from src.handlers.validation import validate_results from src.log import ROOT_LOGGER_NAME from src.models.webhook import Webhook -from src.services.cloud import download_file -from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest -from src.utils.cloud_storage import parse_bucket_url from src.utils.logging import get_function_logger -from src.utils.storage import compose_bucket_filename from src.utils.webhooks import prepare_outgoing_webhook_body, prepare_signed_message module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" @@ -87,142 +68,12 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge "Validating the results" ) - escrow.validate_escrow(webhook.chain_id, webhook.escrow_address) - - manifest = parse_manifest( - escrow.get_escrow_manifest(webhook.chain_id, webhook.escrow_address) - ) - - excor_bucket_host = Config.exchange_oracle_storage_config.provider_endpoint_url() - excor_bucket_name = Config.exchange_oracle_storage_config.results_bucket_name - - excor_annotation_meta_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - annotation.ANNOTATION_METAFILE_NAME, - ) - annotation_metafile_data = download_file( - excor_bucket_host, excor_bucket_name, excor_annotation_meta_path - ) - annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) - - job_annotations: Dict[int, bytes] = {} - for job_meta in annotation_meta.jobs: - job_filename = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - job_meta.annotation_filename, - ) - job_annotations[job_meta.job_id] = download_file( - excor_bucket_host, excor_bucket_name, job_filename - ) - - parsed_gt_bucket_url = parse_bucket_url(manifest.validation.gt_url) - gt_bucket_host = parsed_gt_bucket_url.host_url - gt_bucket_name = parsed_gt_bucket_url.bucket_name - gt_filename = parsed_gt_bucket_url.path - gt_file_data = download_file( - gt_bucket_host, - gt_bucket_name, - gt_filename, - ) - - excor_merged_annotation_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - annotation.RESULTING_ANNOTATIONS_FILE, - ) - merged_annotations = download_file( - excor_bucket_host, excor_bucket_name, excor_merged_annotation_path - ) - - validation_results = process_intermediate_results( - db_session, + validate_results( escrow_address=webhook.escrow_address, chain_id=webhook.chain_id, - meta=annotation_meta, - job_annotations={k: io.BytesIO(v) for k, v in job_annotations.items()}, - merged_annotations=io.BytesIO(merged_annotations), - gt_annotations=io.BytesIO(gt_file_data), - manifest=manifest, - logger=logger, + db_session=db_session, ) - if isinstance(validation_results, ValidationSuccess): - logger.info( - f"Validation for escrow_address={webhook.escrow_address} successful, " - f"average annotation quality is {validation_results.average_quality:.2f}" - ) - - recor_merged_annotations_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - validation.RESULTING_ANNOTATIONS_FILE, - ) - - recor_validation_meta_path = compose_bucket_filename( - webhook.escrow_address, - webhook.chain_id, - validation.VALIDATION_METAFILE_NAME, - ) - validation_metafile = serialize_validation_meta(validation_results.validation_meta) - - storage_client = cloud_client.S3Client( - Config.storage_config.provider_endpoint_url(), - access_key=Config.storage_config.access_key, - secret_key=Config.storage_config.secret_key, - ) - - # TODO: add encryption - storage_client.create_file( - Config.storage_config.results_bucket_name, - recor_merged_annotations_path, - validation_results.resulting_annotations, - ) - storage_client.create_file( - Config.storage_config.results_bucket_name, - recor_validation_meta_path, - validation_metafile, - ) - - escrow.store_results( - webhook.chain_id, - webhook.escrow_address, - Config.storage_config.bucket_url() - + os.path.dirname(recor_merged_annotations_path), - compute_resulting_annotations_hash(validation_results.resulting_annotations), - ) - - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.reputation_oracle, - event=RecordingOracleEvent_TaskCompleted(), - ) - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.exchange_oracle, - event=RecordingOracleEvent_TaskCompleted(), - ) - else: - logger.info( - f"Validation for escrow_address={webhook.escrow_address} failed, " - f"rejected {len(validation_results.rejected_job_ids)} jobs" - ) - - oracle_db_service.outbox.create_webhook( - db_session, - webhook.escrow_address, - webhook.chain_id, - OracleWebhookTypes.exchange_oracle, - event=RecordingOracleEvent_TaskRejected( - rejected_job_ids=validation_results.rejected_job_ids - ), - ) - case _: assert False, f"Unknown exchange oracle event {webhook.event_type}" diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py index 5fb551ee3b..a9eb2d37a9 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py @@ -48,14 +48,13 @@ def process_outgoing_reputation_oracle_webhooks(): timestamp=None, # TODO: reputation oracle doesn't support ) - # FIXME: For a sake of compatability with the current + # TODO: remove compatibility code + # FIXME: For a sake of compatibility with the current # version of Reputation Oracle keep this + # vvv body["escrowAddress"] = body.pop("escrow_address") body["chainId"] = body.pop("chain_id") body["eventType"] = body.pop("event_type") - - # TODO: remove compatibility code - # vvv body.pop("event_data") # ^^^ diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index ad004673b7..5d786b0a28 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -3,18 +3,24 @@ import os from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Type, Union +from typing import Dict, List, Optional, Sequence, Tuple, Type, Union import datumaro as dm import numpy as np from attrs import define from sqlalchemy.orm import Session +import src.core.tasks.boxes_from_points as boxes_from_points_task import src.services.validation as db_service from src.core.annotation_meta import AnnotationMeta +from src.core.config import Config from src.core.manifest import TaskManifest +from src.core.storage import compose_data_bucket_filename from src.core.types import TaskType from src.core.validation_meta import JobMeta, ResultMeta, ValidationMeta +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud.utils import BucketAccessInfo +from src.utils.annotations import shift_ann from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive from src.validation.dataset_comparison import ( BboxDatasetComparator, @@ -39,12 +45,14 @@ class ValidationFailure: TaskType.image_label_binary: "cvat_images", TaskType.image_points: "coco_person_keypoints", TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", } DM_GT_DATASET_FORMAT_MAPPING = { TaskType.image_label_binary: "cvat_images", TaskType.image_points: "coco_instances", # we compare points against boxes TaskType.image_boxes: "coco_instances", + TaskType.image_boxes_from_points: "coco_instances", } @@ -52,68 +60,311 @@ class ValidationFailure: # TaskType.image_label_binary: TagDatasetComparator, # TODO: implement if support is needed TaskType.image_boxes: BboxDatasetComparator, TaskType.image_points: PointsDatasetComparator, + TaskType.image_boxes_from_points: BboxDatasetComparator, } +_JobResults = Dict[int, float] +_RejectedJobs = Sequence[int] -def process_intermediate_results( - session: Session, - *, - escrow_address: str, - chain_id: int, - meta: AnnotationMeta, - job_annotations: Dict[int, io.RawIOBase], - gt_annotations: io.RawIOBase, - merged_annotations: io.RawIOBase, - manifest: TaskManifest, - logger: logging.Logger, -) -> Union[ValidationSuccess, ValidationFailure]: - # validate - task_type = manifest.annotation.type - dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - job_results: Dict[int, float] = {} - rejected_job_ids: List[int] = [] +class _TaskValidator: + def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): + self.escrow_address = escrow_address + self.chain_id = chain_id + self.manifest = manifest + + self.input_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + + self.gt_annotations: Optional[io.IOBase] = None + self.job_annotations: Optional[io.IOBase] = None + self.merged_annotations: Optional[io.IOBase] = None + + def validate(self) -> Tuple[_JobResults, _RejectedJobs]: + assert self.gt_annotations is not None + assert self.job_annotations is not None + assert self.merged_annotations is not None + + manifest = self.manifest + task_type = manifest.annotation.type + dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + + gt_annotations = self.gt_annotations + job_annotations = self.job_annotations + merged_annotations = self.merged_annotations + + job_results: Dict[int, float] = {} + rejected_job_ids: List[int] = [] + + with TemporaryDirectory() as tempdir: + tempdir = Path(tempdir) - with TemporaryDirectory() as tempdir: - tempdir = Path(tempdir) + gt_dataset_path = tempdir / "gt.json" + gt_dataset_path.write_bytes(gt_annotations.read()) + gt_dataset = dm.Dataset.import_from( + os.fspath(gt_dataset_path), format=DM_GT_DATASET_FORMAT_MAPPING[task_type] + ) + + comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( + min_similarity_threshold=manifest.validation.min_quality + ) + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), format=dataset_format + ) + + job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) + job_results[job_cvat_id] = job_mean_accuracy - gt_dataset_path = tempdir / "gt.json" - gt_dataset_path.write_bytes(gt_annotations.read()) - gt_dataset = dm.Dataset.import_from( - os.fspath(gt_dataset_path), format=DM_GT_DATASET_FORMAT_MAPPING[task_type] + if job_mean_accuracy < manifest.validation.min_quality: + rejected_job_ids.append(job_cvat_id) + + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + extract_zip_archive(merged_annotations, merged_dataset_path) + + merged_dataset = dm.Dataset.import_from( + os.fspath(merged_dataset_path), format=merged_dataset_format + ) + put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + + updated_merged_dataset_path = tempdir / "merged_updated" + merged_dataset.export( + updated_merged_dataset_path, merged_dataset_format, save_media=False + ) + + updated_merged_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) + updated_merged_dataset_archive.seek(0) + + return job_results, rejected_job_ids, updated_merged_dataset_archive + + +class _BoxesFromPointsValidator(_TaskValidator): + def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): + super().__init__(escrow_address, chain_id, manifest) + + ( + boxes_to_points_mapping, + roi_filenames, + roi_infos, + gt_dataset, + points_dataset, + ) = self._download_task_meta() + + self.gt_dataset = gt_dataset + self.points_dataset = points_dataset + + point_key_to_sample = { + skeleton.id: sample + for sample in points_dataset + for skeleton in sample.annotations + if isinstance(skeleton, dm.Skeleton) + } + + self.bbox_key_to_sample = { + bbox.id: sample + for sample in gt_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + self.point_key_to_bbox_key = {v: k for k, v in boxes_to_points_mapping.items()} + self.roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + self.roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self.roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self.point_offset_by_roi_id = {} + "Offset from new to old coords, (dx, dy)" + + for roi_info in roi_infos: + point_sample = point_key_to_sample[roi_info.point_id] + old_point = next( + skeleton + for skeleton in point_sample.annotations + if skeleton.id == roi_info.point_id + if isinstance(skeleton, dm.Skeleton) + ).elements[0] + old_x, old_y = old_point.points[:2] + offset_x = old_x - roi_info.point_x + offset_y = old_y - roi_info.point_y + self.point_offset_by_roi_id[roi_info.point_id] = (offset_x, offset_y) + + def _download_task_meta(self): + layout = boxes_from_points_task.TaskMetaLayout() + serializer = boxes_from_points_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.from_raw_url( + Config.exchange_oracle_storage_config.bucket_url() + ) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + storage_client = make_cloud_client(oracle_data_bucket) + + boxes_to_points_mapping = serializer.parse_bbox_point_mapping( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BBOX_POINT_MAPPING_FILENAME + ), + ) ) - comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) ) - for job_cvat_id, job_annotations_file in job_annotations.items(): - job_dataset_path = tempdir / str(job_cvat_id) - extract_zip_archive(job_annotations_file, job_dataset_path) + rois = serializer.parse_roi_info( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) - job_dataset = dm.Dataset.import_from(os.fspath(job_dataset_path), format=dataset_format) + gt_dataset = serializer.parse_gt_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.GT_FILENAME + ), + ) + ) - job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) - job_results[job_cvat_id] = job_mean_accuracy + points_dataset = serializer.parse_points_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINTS_FILENAME + ), + ) + ) - if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) + return boxes_to_points_mapping, roi_filenames, rois, gt_dataset, points_dataset - merged_dataset_path = tempdir / str("merged") - merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - extract_zip_archive(merged_annotations, merged_dataset_path) + def _make_gt_dataset_for_job(self, job_dataset: dm.Dataset) -> dm.Dataset: + job_gt_dataset = dm.Dataset(categories=self.gt_dataset.categories(), media_type=dm.Image) - merged_dataset = dm.Dataset.import_from( - os.fspath(merged_dataset_path), format=merged_dataset_format - ) - put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + for job_sample in job_dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + + point_bbox_key = self.point_key_to_bbox_key.get(roi_info.point_id, None) + if point_bbox_key is None: + continue # roi is not from GT set + + bbox_sample = self.bbox_key_to_sample[point_bbox_key] + + bbox = next(bbox for bbox in bbox_sample.annotations if bbox.id == point_bbox_key) + roi_shift_x, roi_shift_y = self.point_offset_by_roi_id[roi_info.point_id] + + bbox_in_roi_coords = shift_ann( + bbox, + offset_x=-roi_shift_x, + offset_y=-roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + job_gt_dataset.put(job_sample.wrap(annotations=[bbox_in_roi_coords])) + + return job_gt_dataset + + def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: + assert self.job_annotations is not None + assert self.merged_annotations is not None + + manifest = self.manifest + task_type = manifest.annotation.type + dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + + job_annotations = self.job_annotations + merged_annotations = self.merged_annotations - updated_merged_dataset_path = tempdir / str("merged_updated") - merged_dataset.export(updated_merged_dataset_path, merged_dataset_format, save_media=False) + job_results: Dict[int, float] = {} + rejected_job_ids: List[int] = [] - updated_merged_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) - updated_merged_dataset_archive.seek(0) + with TemporaryDirectory() as tempdir: + tempdir = Path(tempdir) + + comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( + min_similarity_threshold=manifest.validation.min_quality + ) + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), format=dataset_format + ) + job_gt_dataset = self._make_gt_dataset_for_job(job_dataset) + + job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) + job_results[job_cvat_id] = job_mean_accuracy + + if job_mean_accuracy < manifest.validation.min_quality: + rejected_job_ids.append(job_cvat_id) + + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + extract_zip_archive(merged_annotations, merged_dataset_path) + + merged_dataset = dm.Dataset.import_from( + os.fspath(merged_dataset_path), format=merged_dataset_format + ) + put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) + + updated_merged_dataset_path = tempdir / "merged_updated" + merged_dataset.export( + updated_merged_dataset_path, merged_dataset_format, save_media=False + ) + + updated_merged_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) + updated_merged_dataset_archive.seek(0) + + return job_results, rejected_job_ids, updated_merged_dataset_archive + + +def process_intermediate_results( + session: Session, + *, + escrow_address: str, + chain_id: int, + meta: AnnotationMeta, + job_annotations: Dict[int, io.RawIOBase], + gt_annotations: io.RawIOBase, + merged_annotations: io.RawIOBase, + manifest: TaskManifest, + logger: logging.Logger, +) -> Union[ValidationSuccess, ValidationFailure]: + # validate + task_type = manifest.annotation.type + if task_type in [TaskType.image_label_binary, TaskType.image_boxes, TaskType.image_points]: + validator_type = _TaskValidator + elif task_type == TaskType.image_boxes_from_points: + validator_type = _BoxesFromPointsValidator + else: + raise Exception(f"Unknown task type {task_type}") + + validator = validator_type(escrow_address=escrow_address, chain_id=chain_id, manifest=manifest) + validator.gt_annotations = gt_annotations + validator.job_annotations = job_annotations + validator.merged_annotations = merged_annotations + job_results, rejected_job_ids, updated_merged_dataset_archive = validator.validate() if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -212,6 +463,8 @@ def put_gt_into_merged_dataset( merged_dataset.put(sample.wrap(annotations=annotations)) case TaskType.image_label_binary.value: merged_dataset.update(gt_dataset) + case TaskType.image_boxes_from_points: + merged_dataset.update(gt_dataset) case _: assert False, f"Unknown task type {manifest.annotation.type}" diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py new file mode 100644 index 0000000000..ec9030f8ff --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -0,0 +1,235 @@ +import io +import os +from logging import Logger +from typing import Dict, Optional, Union + +from sqlalchemy.orm import Session + +import src.chain.escrow as escrow +import src.core.annotation_meta as annotation +import src.core.validation_meta as validation +import src.services.webhook as oracle_db_service +from src.core.config import Config +from src.core.manifest import TaskManifest +from src.core.oracle_events import ( + RecordingOracleEvent_TaskCompleted, + RecordingOracleEvent_TaskRejected, +) +from src.core.storage import ( + compose_results_bucket_filename as compose_annotation_results_bucket_filename, +) +from src.core.types import OracleWebhookTypes +from src.handlers.process_intermediate_results import ( + ValidationSuccess, + parse_annotation_metafile, + process_intermediate_results, + serialize_validation_meta, +) +from src.log import ROOT_LOGGER_NAME +from src.services.cloud import make_client as make_cloud_client +from src.services.cloud import s3 +from src.services.cloud.utils import BucketAccessInfo +from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest +from src.utils.logging import NullLogger, get_function_logger +from validators import ValidationFailure + +module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" + + +class _TaskValidator: + def __init__( + self, escrow_address: str, chain_id: int, manifest: TaskManifest, db_session: Session + ) -> None: + self.escrow_address = escrow_address + self.chain_id = chain_id + self.manifest = manifest + self.db_session = db_session + self.logger: Logger = NullLogger() + + self.data_bucket = BucketAccessInfo.from_raw_url( + Config.exchange_oracle_storage_config.bucket_url() + ) + + self.annotation_meta: Optional[annotation.AnnotationMeta] = None + self.job_annotations: Optional[Dict[int, bytes]] = None + self.merged_annotations: Optional[bytes] = None + self.gt_data: Optional[bytes] = None + + def set_logger(self, logger: Logger): + self.logger = logger + + def _download_results_meta(self): + data_bucket_client = make_cloud_client(self.data_bucket) + + annotation_meta_path = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + annotation.ANNOTATION_RESULTS_METAFILE_NAME, + ) + annotation_metafile_data = data_bucket_client.download_file( + self.data_bucket.url.bucket_name, annotation_meta_path + ) + self.annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) + + def _download_annotations(self): + assert self.annotation_meta is not None + + data_bucket_client = make_cloud_client(self.data_bucket) + + job_annotations = {} + for job_meta in self.annotation_meta.jobs: + job_filename = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + job_meta.annotation_filename, + ) + job_annotations[job_meta.job_id] = data_bucket_client.download_file( + self.data_bucket.url.bucket_name, job_filename + ) + + excor_merged_annotation_path = compose_annotation_results_bucket_filename( + self.escrow_address, + self.chain_id, + annotation.RESULTING_ANNOTATIONS_FILE, + ) + merged_annotations = data_bucket_client.download_file( + self.data_bucket.url.bucket_name, excor_merged_annotation_path + ) + + self.job_annotations = job_annotations + self.merged_annotations = merged_annotations + + def _download_gt(self): + gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + gt_bucket_client = make_cloud_client(gt_bucket) + self.gt_data = gt_bucket_client.download_file(gt_bucket.url.bucket_name, gt_bucket.url.path) + + def _download_results(self): + self._download_results_meta() + self._download_annotations() + self._download_gt() + + ValidationResult = Union[ValidationSuccess, ValidationFailure] + + def _process_annotation_results(self) -> ValidationResult: + assert self.annotation_meta is not None + assert self.job_annotations is not None + assert self.merged_annotations is not None + assert self.gt_data is not None + + # TODO: refactor further + return process_intermediate_results( + session=self.db_session, + escrow_address=self.escrow_address, + chain_id=self.chain_id, + meta=self.annotation_meta, + job_annotations={k: io.BytesIO(v) for k, v in self.job_annotations.items()}, + merged_annotations=io.BytesIO(self.merged_annotations), + gt_annotations=io.BytesIO(self.gt_data), + manifest=self.manifest, + logger=self.logger, + ) + + def validate(self): + self._download_results() + + validation_result = self._process_annotation_results() + + self._handle_validation_result(validation_result) + + def _compose_validation_results_bucket_filename(self, filename: str) -> str: + return f"{self.escrow_address}@{self.chain_id}/{filename}" + + def _handle_validation_result(self, validation_result: ValidationResult): + logger = self.logger + escrow_address = self.escrow_address + chain_id = self.chain_id + db_session = self.db_session + + if isinstance(validation_result, ValidationSuccess): + logger.info( + f"Validation for escrow_address={escrow_address} successful, " + f"average annotation quality is {validation_result.average_quality:.2f}" + ) + + recor_merged_annotations_path = self._compose_validation_results_bucket_filename( + validation.RESULTING_ANNOTATIONS_FILE, + ) + + recor_validation_meta_path = self._compose_validation_results_bucket_filename( + validation.VALIDATION_METAFILE_NAME, + ) + validation_metafile = serialize_validation_meta(validation_result.validation_meta) + + storage_client = s3.S3Client( + Config.storage_config.provider_endpoint_url(), + access_key=Config.storage_config.access_key, + secret_key=Config.storage_config.secret_key, + ) + + # TODO: add encryption + storage_client.create_file( + Config.storage_config.data_bucket_name, + recor_merged_annotations_path, + validation_result.resulting_annotations, + ) + storage_client.create_file( + Config.storage_config.data_bucket_name, + recor_validation_meta_path, + validation_metafile, + ) + + escrow.store_results( + chain_id, + escrow_address, + Config.storage_config.bucket_url() + os.path.dirname(recor_merged_annotations_path), + compute_resulting_annotations_hash(validation_result.resulting_annotations), + ) + + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.reputation_oracle, + event=RecordingOracleEvent_TaskCompleted(), + ) + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.exchange_oracle, + event=RecordingOracleEvent_TaskCompleted(), + ) + else: + logger.info( + f"Validation for escrow_address={escrow_address} failed, " + f"rejected {len(validation_result.rejected_job_ids)} jobs" + ) + + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.exchange_oracle, + event=RecordingOracleEvent_TaskRejected( + rejected_job_ids=validation_result.rejected_job_ids + ), + ) + + +def validate_results( + escrow_address: str, + chain_id: int, + db_session: Session, +): + logger = get_function_logger(module_logger_name) + + escrow.validate_escrow(chain_id=chain_id, escrow_address=escrow_address) + + manifest = parse_manifest(escrow.get_escrow_manifest(chain_id, escrow_address)) + + validator = _TaskValidator( + escrow_address=escrow_address, chain_id=chain_id, manifest=manifest, db_session=db_session + ) + validator.set_logger(logger) + validator.validate() diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py index eb128cc100..007394d9ed 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py @@ -1,13 +1,3 @@ -from typing import Optional - -from src.services.cloud.client import S3Client - - -def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: - client = S3Client(bucket_host) - return client.download_fileobj(bucket_name, filename) - - -def list_files(bucket_host: str, bucket_name: str, path: Optional[str] = None) -> list[str]: - client = S3Client(bucket_host) - return [f.key for f in client.list_files(bucket_name, path=path)] +from src.services.cloud.client import StorageClient +from src.services.cloud.types import CloudProviders +from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py index 2c6101a573..0a80d1706e 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py @@ -1,61 +1,24 @@ -# Copyright (C) 2022 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - -from io import BytesIO +from abc import ABCMeta, abstractmethod from typing import List, Optional -import boto3 -from botocore.exceptions import ClientError -from botocore.handlers import disable_signing - - -class S3Client: - def __init__( - self, - endpoint_url: str, - *, - access_key: Optional[str] = None, - secret_key: Optional[str] = None, - ) -> None: - s3 = boto3.resource( - "s3", - **(dict(aws_access_key_id=access_key) if access_key else {}), - **(dict(aws_secret_access_key=secret_key) if secret_key else {}), - endpoint_url=endpoint_url, - ) - - self.resource = s3 - self.client = s3.meta.client - - if not access_key and not secret_key: - self.client.meta.events.register("choose-signer.s3.*", disable_signing) +class StorageClient(metaclass=ABCMeta): + @abstractmethod def create_file(self, bucket: str, filename: str, data: bytes = b""): - self.client.put_object(Body=data, Bucket=bucket, Key=filename) + ... + @abstractmethod def remove_file(self, bucket: str, filename: str): - self.client.delete_object(Bucket=bucket, Key=filename) + ... + @abstractmethod def file_exists(self, bucket: str, filename: str) -> bool: - try: - self.client.head_object(Bucket=bucket, Key=filename) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - else: - raise + ... - def download_fileobj(self, bucket: str, key: str) -> bytes: - with BytesIO() as data: - self.client.download_fileobj(Bucket=bucket, Key=key, Fileobj=data) - return data.getvalue() + @abstractmethod + def download_file(self, bucket: str, key: str) -> bytes: + ... - def list_files(self, bucket: str, path: Optional[str] = None) -> List: - objects = self.resource.Bucket(bucket).objects - if path: - objects = objects.filter(Prefix=path.strip("/\\") + "/") - else: - objects = objects.all() - return list(objects) + @abstractmethod + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + ... diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py new file mode 100644 index 0000000000..ed00f2799e --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 CVAT.ai Corporation +# +# SPDX-License-Identifier: MIT + +from dataclasses import dataclass +from io import BytesIO +from typing import List, Optional +from urllib.parse import unquote + +import boto3 +from botocore.exceptions import ClientError +from botocore.handlers import disable_signing + +from src.services.cloud.client import StorageClient +from src.services.cloud.types import BucketCredentials + + +class S3Client(StorageClient): + def __init__( + self, + endpoint_url: str, + *, + access_key: Optional[str] = None, + secret_key: Optional[str] = None, + ) -> None: + s3 = boto3.resource( + "s3", + **(dict(aws_access_key_id=access_key) if access_key else {}), + **(dict(aws_secret_access_key=secret_key) if secret_key else {}), + endpoint_url=unquote(endpoint_url), + ) + + self.resource = s3 + self.client = s3.meta.client + + if not access_key and not secret_key: + self.client.meta.events.register("choose-signer.s3.*", disable_signing) + + def create_file(self, bucket: str, filename: str, data: bytes = b""): + self.client.put_object(Body=data, Bucket=unquote(bucket), Key=unquote(filename)) + + def remove_file(self, bucket: str, filename: str): + self.client.delete_object(Bucket=unquote(bucket), Key=unquote(filename)) + + def file_exists(self, bucket: str, filename: str) -> bool: + try: + self.client.head_object(Bucket=unquote(bucket), Key=unquote(filename)) + return True + except ClientError as e: + if e.response["Error"]["Code"] == "404": + return False + else: + raise + + def download_file(self, bucket: str, key: str) -> bytes: + with BytesIO() as data: + self.client.download_fileobj(Bucket=unquote(bucket), Key=unquote(key), Fileobj=data) + return data.getvalue() + + def list_files(self, bucket: str, *, prefix: Optional[str] = None) -> List: + objects = self.resource.Bucket(unquote(bucket)).objects + if prefix: + objects = objects.filter(Prefix=unquote(prefix).strip("/\\") + "/") + else: + objects = objects.all() + return list(objects) + + def list_filenames(self, bucket: str, *, prefix: Optional[str] = None) -> List[str]: + return [file_info.key for file_info in self.list_files(bucket=bucket, prefix=prefix)] + + +@dataclass +class S3BucketCredentials(BucketCredentials): + access_key: str + secret_key: str + + +DEFAULT_S3_HOST = "s3.amazonaws.com" + + +def download_file(bucket_host: str, bucket_name: str, filename: str) -> bytes: + client = S3Client(bucket_host) + return client.download_file(bucket_name, filename) + + +def list_files(bucket_host: str, bucket_name: str, *, prefix: Optional[str] = None) -> List[str]: + client = S3Client(bucket_host) + return client.list_filenames(bucket_name, prefix=prefix) diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py new file mode 100644 index 0000000000..ee89883d52 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum, auto +from typing import Optional + +from src.utils.enums import BetterEnumMeta + + +class CloudProviders(Enum, metaclass=BetterEnumMeta): + aws = auto() + gcs = auto() + + +@dataclass +class BucketUrl: + provider: CloudProviders + host_url: str + bucket_name: str + path: str + + +class BucketCredentials: + pass + + +@dataclass +class BucketAccessInfo: + url: BucketUrl + credentials: Optional[BucketCredentials] = None + + @classmethod + def from_raw_url(cls, url: str) -> BucketAccessInfo: + from src.services.cloud.utils import parse_bucket_url + + return cls.from_parsed_url(parse_bucket_url(url)) + + @classmethod + def from_parsed_url(cls, parsed_url: BucketUrl) -> BucketAccessInfo: + return BucketAccessInfo(url=parsed_url) + + @property + def provider(self) -> CloudProviders: + return self.url.provider diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py new file mode 100644 index 0000000000..33b9a4f7ee --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py @@ -0,0 +1,96 @@ +from typing import Optional, Union, overload +from urllib.parse import urlparse + +from src.core.config import Config +from src.services.cloud.client import StorageClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, BucketUrl, CloudProviders +from src.utils.net import is_ipv4 + + +def parse_bucket_url(data_url: str) -> BucketUrl: + parsed_url = urlparse(data_url) + + if parsed_url.netloc.endswith(DEFAULT_S3_HOST): + # AWS S3 bucket + return BucketUrl( + provider=CloudProviders.aws, + host_url=f"https://{DEFAULT_S3_HOST}", + bucket_name=parsed_url.netloc.split(".")[0], + path=parsed_url.path.lstrip("/"), + ) + # elif parsed_url.netloc.endswith("storage.googleapis.com"): + # # Google Cloud Storage (GCS) bucket + # return ParsedBucketUrl( + # provider=CloudProviders.gcs, + # bucket_name=parsed_url.netloc.split(".")[0], + # ) + elif Config.features.enable_custom_cloud_host: + if is_ipv4(parsed_url.netloc): + host = parsed_url.netloc + bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + else: + host = parsed_url.netloc.partition(".")[2] + bucket_name = parsed_url.netloc.split(".")[0] + path = parsed_url.path.lstrip("/") + + return BucketUrl( + provider=CloudProviders.aws, + host_url=f"{parsed_url.scheme}://{host}", + bucket_name=bucket_name, + path=path, + ) + else: + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") + + +def compose_bucket_url( + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None +) -> str: + match provider: + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" + + +@overload +def make_client(url: BucketUrl, credentials: Optional[BucketCredentials] = None) -> StorageClient: + ... + + +@overload +def make_client( + bucket_info: BucketAccessInfo, +) -> StorageClient: + ... + + +def make_client( + _pos1: Union[BucketUrl, BucketAccessInfo, None] = None, + *, + bucket_info: Optional[BucketAccessInfo] = None, + url: Optional[BucketUrl] = None, + credentials: Optional[BucketCredentials] = None, +) -> StorageClient: + if _pos1 is not None: + if isinstance(_pos1, BucketAccessInfo): + bucket_info = _pos1 + else: + url = _pos1 + + if bucket_info is None: + bucket_info = BucketAccessInfo(url=url, credentials=credentials) + + match bucket_info.provider: + case CloudProviders.aws: + client_kwargs = {} + if bucket_info.credentials: + client_kwargs["access_key"] = bucket_info.credentials.access_key + client_kwargs["secret_key"] = bucket_info.credentials.secret_key + + client = S3Client(bucket_info.url.host_url, **client_kwargs) + case _: + raise Exception("Unsupported cloud provider") + + return client diff --git a/packages/examples/cvat/recording-oracle/src/utils/annotations.py b/packages/examples/cvat/recording-oracle/src/utils/annotations.py new file mode 100644 index 0000000000..5a62b80fb4 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/utils/annotations.py @@ -0,0 +1,39 @@ +import datumaro as dm +import numpy as np + + +def shift_ann( + ann: dm.Annotation, offset_x: float, offset_y: float, *, img_w: int, img_h: int +) -> dm.Annotation: + "Shift annotation coordinates with clipping to the image size" + + if isinstance(ann, dm.Bbox): + shifted_ann = ann.wrap( + x=offset_x + ann.x, + y=offset_y + ann.y, + ) + elif isinstance(ann, dm.Points): + shifted_ann = ann.wrap( + points=np.clip( + np.reshape(ann.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + elif isinstance(ann, dm.Skeleton): + shifted_ann = ann.wrap( + elements=[ + point.wrap( + points=np.clip( + np.reshape(point.points, (-1, 2)) + (offset_x, offset_y), + 0, + [img_w, img_h], + ).flat + ) + for point in ann.elements + ] + ) + else: + assert False, f"Unsupported annotation type '{ann.type}'" + + return shifted_ann diff --git a/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py b/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py deleted file mode 100644 index 3418fde5bc..0000000000 --- a/packages/examples/cvat/recording-oracle/src/utils/cloud_storage.py +++ /dev/null @@ -1,64 +0,0 @@ -from dataclasses import dataclass -from typing import Optional -from urllib.parse import urlparse - -from src.core.config import Config -from src.core.types import CloudProviders -from src.utils.net import is_ipv4 - - -@dataclass -class ParsedBucketUrl: - provider: str - host_url: str - bucket_name: str - path: str - - -DEFAULT_S3_HOST = "s3.amazonaws.com" - - -def parse_bucket_url(data_url: str) -> ParsedBucketUrl: - parsed_url = urlparse(data_url) - - if parsed_url.netloc.endswith(DEFAULT_S3_HOST): - # AWS S3 bucket - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"https://{DEFAULT_S3_HOST}", - bucket_name=parsed_url.netloc.split(".")[0], - path=parsed_url.path.lstrip("/"), - ) - # elif parsed_url.netloc.endswith("storage.googleapis.com"): - # # Google Cloud Storage (GCS) bucket - # return ParsedBucketUrl( - # provider=CloudProviders.gcs.value, - # bucket_name=parsed_url.netloc.split(".")[0], - # ) - elif Config.features.enable_custom_cloud_host: - if is_ipv4(parsed_url.netloc): - host = parsed_url.netloc - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) - else: - host = parsed_url.netloc.partition(".")[2] - bucket_name = parsed_url.netloc.split(".")[0] - path = parsed_url.path.lstrip("/") - - return ParsedBucketUrl( - provider=CloudProviders.aws.value, - host_url=f"{parsed_url.scheme}://{host}", - bucket_name=bucket_name, - path=path, - ) - else: - raise ValueError(f"{parsed_url.netloc} cloud provider is not supported by CVAT") - - -def compose_bucket_url( - bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None -) -> str: - match provider: - case CloudProviders.aws.value: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProviders.gcs.value: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" diff --git a/packages/examples/cvat/recording-oracle/src/utils/logging.py b/packages/examples/cvat/recording-oracle/src/utils/logging.py index d9ce28d024..146f6c219b 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/logging.py +++ b/packages/examples/cvat/recording-oracle/src/utils/logging.py @@ -20,3 +20,9 @@ def get_function_logger( function_name = current_function_name(depth=2) return parent_logger.getChild(function_name) + + +class NullLogger(logging.Logger): + def __init__(self, name: str = "", level=0) -> None: + super().__init__(name, level) + self.disabled = True diff --git a/packages/examples/cvat/recording-oracle/src/utils/storage.py b/packages/examples/cvat/recording-oracle/src/utils/storage.py deleted file mode 100644 index 2ce5d70eec..0000000000 --- a/packages/examples/cvat/recording-oracle/src/utils/storage.py +++ /dev/null @@ -1,5 +0,0 @@ -from src.core.types import Networks - - -def compose_bucket_filename(escrow_address: str, chain_id: Networks, filename: str) -> str: - return f"{escrow_address}@{chain_id}/{filename}" diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 9ecb640060..884478ef56 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -1,9 +1,10 @@ import itertools +from abc import ABCMeta, abstractmethod from typing import Any, Callable, Dict, Tuple import datumaro as dm import numpy as np -from attrs import define, field +from attrs import define from .annotation_matching import Bbox, Point, bbox_iou, match_annotations, point_to_bbox_cmp @@ -31,9 +32,10 @@ def __init__(self, inner: Callable) -> None: @define -class DatasetComparator: +class DatasetComparator(metaclass=ABCMeta): min_similarity_threshold: float + @abstractmethod def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: ... diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index eea670e687..5cafd8ec8c 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -19,7 +19,7 @@ from tests.utils.constants import ( DEFAULT_GAS_PAYER_PRIV, DEFAULT_HASH, - DEFAULT_URL, + DEFAULT_MANIFEST_URL, REPUTATION_ORACLE_ADDRESS, ) from tests.utils.setup_escrow import ( @@ -108,13 +108,13 @@ def test_store_results(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 results = store_results( - self.w3.eth.chain_id, escrow_address, DEFAULT_URL, DEFAULT_HASH + self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, DEFAULT_HASH ) self.assertIsNone(results) intermediate_results_url = get_intermediate_results_url( self.w3, escrow_address ) - self.assertEqual(intermediate_results_url, DEFAULT_URL) + self.assertEqual(intermediate_results_url, DEFAULT_MANIFEST_URL) def test_store_results_invalid_url(self): escrow_address = create_escrow(self.w3) @@ -131,7 +131,7 @@ def test_store_results_invalid_hash(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 with self.assertRaises(EscrowClientError) as error: - store_results(self.w3.eth.chain_id, escrow_address, DEFAULT_URL, "") + store_results(self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, "") self.assertEqual(f"Invalid empty hash", str(error.exception)) def test_get_reputation_oracle_address(self): diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py index 4f99224538..dc8ff0bb0a 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py @@ -9,7 +9,7 @@ from src.chain.kvstore import get_reputation_oracle_url, get_role_by_address -from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, DEFAULT_URL, REPUTATION_ORACLE_ADDRESS +from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, DEFAULT_MANIFEST_URL, REPUTATION_ORACLE_ADDRESS from tests.utils.setup_escrow import create_escrow from tests.utils.setup_kvstore import store_kvstore_value @@ -28,7 +28,7 @@ def setUp(self): def test_get_reputation_oracle_url(self): escrow_address = create_escrow(self.w3) - store_kvstore_value("webhook_url", DEFAULT_URL) + store_kvstore_value("webhook_url", DEFAULT_MANIFEST_URL) with ( patch("src.chain.kvstore.get_web3") as mock_get_web3, @@ -39,12 +39,12 @@ def test_get_reputation_oracle_url(self): mock_escrow = Mock() mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow - mock_leader.return_value = MagicMock(webhook_url=DEFAULT_URL) + mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) reputation_url = get_reputation_oracle_url( self.w3.eth.chain_id, escrow_address ) - self.assertEqual(reputation_url, DEFAULT_URL) + self.assertEqual(reputation_url, DEFAULT_MANIFEST_URL) def test_get_reputation_oracle_url_invalid_escrow(self): with patch("src.chain.kvstore.get_web3") as mock_function: @@ -62,7 +62,7 @@ def test_get_reputation_oracle_url_invalid_address(self): ): mock_get_web3.return_value = self.w3 mock_escrow = Mock() - mock_escrow.reputationOracle = REPUTATION_ORACLE_ADDRESS + mock_escrow.reputation_oracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow mock_leader.return_value = MagicMock(webhook_url="") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index a71ec3d0d4..4ec2d2f3a3 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -4,7 +4,7 @@ import pytest from botocore.exceptions import ClientError, EndpointConnectionError -from src.services.cloud import S3Client +from src.services.cloud.s3 import S3Client class ServiceIntegrationTest(unittest.TestCase): @@ -37,7 +37,7 @@ def test_file_operations(self): assert client.file_exists(self.bucket_name, file_name) assert len(client.list_files(self.bucket_name)) == 1 - file_content = client.download_fileobj(bucket=self.bucket_name, key=file_name) + file_content = client.download_file(bucket=self.bucket_name, key=file_name) assert file_content == data client.remove_file(self.bucket_name, file_name) @@ -49,10 +49,10 @@ def test_degenerate_file_operations(self): invalid_file = "non-existent-file" with pytest.raises(ClientError): - client.download_fileobj(bucket=invalid_bucket, key=invalid_file) + client.download_file(bucket=invalid_bucket, key=invalid_file) with pytest.raises(ClientError): - client.download_fileobj(bucket=self.bucket_name, key=invalid_file) + client.download_file(bucket=self.bucket_name, key=invalid_file) with pytest.raises(ClientError): client.create_file(bucket=invalid_bucket, filename=invalid_file) diff --git a/packages/examples/cvat/recording-oracle/tests/utils/constants.py b/packages/examples/cvat/recording-oracle/tests/utils/constants.py index 07dde21a4a..e5ff716440 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/constants.py @@ -4,6 +4,7 @@ RECORDING_ORACLE_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" RECORDING_ORACLE_FEE = 10 +REPUTATION_ORACLE_WEBHOOK_URL = 'http://localhost:5001/webhook/cvat' REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_PRIV = "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" REPUTATION_ORACLE_FEE = 10 @@ -11,7 +12,7 @@ EXCHANGE_ORACLE_ADDRESS = "0x90F79bf6EB2c4f870365E785982E1f101E93b906" EXCHANGE_ORACLE_FEE = 10 -DEFAULT_URL = "http://host.docker.internal:9000/manifests/manifest.json" +DEFAULT_MANIFEST_URL = "http://host.docker.internal:9000/manifests/manifest.json" DEFAULT_HASH = "test" SIGNATURE = "0xa0c5626301e3c198cb91356e492890c0c28db8c37044846134939246911a693c4d7116d04aa4bc40a41077493868b8dd533d30980f6addb28d1b3610a84cb4091c" diff --git a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py index a225373349..17af89c712 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py @@ -7,7 +7,7 @@ from tests.utils.constants import ( DEFAULT_HASH, - DEFAULT_URL, + DEFAULT_MANIFEST_URL, EXCHANGE_ORACLE_ADDRESS, EXCHANGE_ORACLE_FEE, JOB_REQUESTER_ID, @@ -36,7 +36,7 @@ def create_escrow(web3: Web3): recording_oracle_fee=RECORDING_ORACLE_FEE, reputation_oracle_address=REPUTATION_ORACLE_ADDRESS, reputation_oracle_fee=REPUTATION_ORACLE_FEE, - manifest_url=DEFAULT_URL, + manifest_url=DEFAULT_MANIFEST_URL, hash=DEFAULT_HASH, ), ) @@ -50,7 +50,7 @@ def fund_escrow(web3: Web3, escrow_address: str): def bulk_payout(web3: Web3, escrow_address: str, recipient: str, amount: Decimal): escrow_client = EscrowClient(web3) - escrow_client.bulk_payout(escrow_address, [recipient], [amount], DEFAULT_URL, DEFAULT_HASH, 1) + escrow_client.bulk_payout(escrow_address, [recipient], [amount], DEFAULT_MANIFEST_URL, DEFAULT_HASH, 1) def get_intermediate_results_url(web3: Web3, escrow_address: str): From bd43c8488cdc41d7627e709aa5e9d45bfa3d4803 Mon Sep 17 00:00:00 2001 From: maya Date: Tue, 20 Feb 2024 12:57:53 +0100 Subject: [PATCH 23/82] Use virtual hosted bucket style --- .../cvat/exchange-oracle/src/services/cloud/types.py | 9 ++++----- .../cvat/recording-oracle/src/services/cloud/types.py | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py index 9a793c3403..1c3cb2e87b 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -93,14 +93,13 @@ def from_url(cls, data: str) -> BucketAccessInfo: path=parsed_url.path.lstrip("/"), ) elif parsed_url.netloc.endswith("storage.googleapis.com"): - # TODO # Google Cloud Storage (GCS) bucket - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + # NOTE: virtual hosted-style is expected (https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME) return BucketAccessInfo( provider=CloudProvider.gcs, - bucket_name=bucket_name, - host_url=f"{parsed_url.scheme}://{parsed_url.netloc}", - path=path, + bucket_name=parsed_url.netloc[: -len(".storage.googleapis.com")], + host_url=f"{parsed_url.scheme}://storage.googleapis.com", + path=parsed_url.path.lstrip("/"), ) elif Config.features.enable_custom_cloud_host: if is_ipv4(parsed_url.netloc): diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py index 6d285fdf3c..74d4fabd07 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -95,14 +95,13 @@ def from_url(cls, data: str) -> BucketAccessInfo: path=parsed_url.path.lstrip("/"), ) elif parsed_url.netloc.endswith("storage.googleapis.com"): - # TODO # Google Cloud Storage (GCS) bucket - bucket_name, path = parsed_url.path.lstrip("/").split("/", maxsplit=1) + # NOTE: virtual hosted-style is expected (https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME) return BucketAccessInfo( provider=CloudProvider.gcs, - bucket_name=bucket_name, - host_url=f"{parsed_url.scheme}://{parsed_url.netloc}", - path=path, + bucket_name=parsed_url.netloc[: -len(".storage.googleapis.com")], + host_url=f"{parsed_url.scheme}://storage.googleapis.com", + path=parsed_url.path.lstrip("/"), ) elif Config.features.enable_custom_cloud_host: if is_ipv4(parsed_url.netloc): From 2c198638db4ce8ca73d4d5d80979910cb6d0e83d Mon Sep 17 00:00:00 2001 From: maya Date: Tue, 20 Feb 2024 12:59:15 +0100 Subject: [PATCH 24/82] Fix creating CS in CVAT --- .../exchange-oracle/src/cvat/api_calls.py | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 3914d080fc..957a8267aa 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -1,14 +1,13 @@ import io +import json import logging import zipfile -import json - -from io import BytesIO from datetime import timedelta from enum import Enum from http import HTTPStatus +from io import BytesIO from time import sleep -from typing import List, Optional, Tuple, Dict, Any +from typing import Any, Dict, List, Optional, Tuple from cvat_sdk.api_client import ApiClient, Configuration, exceptions, models from cvat_sdk.api_client.api_client import Endpoint @@ -115,23 +114,34 @@ def create_cloudstorage( # CVAT credentials: key | secret_key | key_file def to_cvat_credentials() -> Dict: credentials_ = dict() - for cvat_field, field in zip(('key', 'secret_key', 'key_file'), ('access_key', 'secret_key', 'service_account_key')): - if (value := credentials.get(field)): - if cvat_field == 'key_file': - key_file = BytesIO(json.dumps(value).encode('utf-8')) - key_file.name = 'key_file.json' + for cvat_field, field in zip( + ("key", "secret_key", "key_file"), ("access_key", "secret_key", "service_account_key") + ): + if value := credentials.get(field): + if cvat_field == "key_file": + key_file = BytesIO(json.dumps(value).encode("utf-8")) + key_file.name = "key_file.json" key_file.seek(0) credentials_[cvat_field] = key_file else: credentials_[cvat_field] = value return credentials_ - converted_credentials = to_cvat_credentials() - if converted_credentials: - credentials_type = models.CredentialsTypeEnum("KEY_SECRET_KEY_PAIR") if provider == "AWS_S3_BUCKET" \ + request_kwargs = dict() + + if credentials: + request_kwargs.update(to_cvat_credentials()) + credentials_type = ( + models.CredentialsTypeEnum("KEY_SECRET_KEY_PAIR") + if provider == "AWS_S3_BUCKET" else models.CredentialsTypeEnum("KEY_FILE_PATH") + ) else: credentials_type = models.CredentialsTypeEnum("ANONYMOUS_ACCESS") + + if bucket_host: + request_kwargs["specific_attributes"] = f"endpoint_url={bucket_host}" + logger = logging.getLogger("app") with get_api_client() as api_client: @@ -141,8 +151,7 @@ def to_cvat_credentials() -> Dict: display_name=bucket_name, credentials_type=credentials_type, description=bucket_name, - **({"specific_attributes": f"endpoint_url={bucket_host}"} if bucket_host else {}), - **(converted_credentials if converted_credentials else {}), + **request_kwargs, ) # CloudStorageWriteRequest try: (data, response) = api_client.cloudstorages_api.create( From 2d57d96af21b829b39b074d807025c69f628f467 Mon Sep 17 00:00:00 2001 From: maya Date: Tue, 20 Feb 2024 13:48:55 +0100 Subject: [PATCH 25/82] Fix tests --- .../cvat/exchange-oracle/src/services/cloud/__init__.py | 1 + .../cron/state_trackers/test_retrieve_annotations.py | 8 ++++---- .../cron/test_process_job_launcher_webhooks.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py index b04cfab903..a1362d3db1 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py @@ -1,3 +1,4 @@ from src.services.cloud.client import StorageClient +from src.services.cloud.s3 import S3Client from src.services.cloud.types import CloudProvider from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py index bf467e09aa..0678178529 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py @@ -102,7 +102,7 @@ def test_retrieve_annotations(self): patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, patch("src.crons.state_trackers.cvat_api") as mock_cvat_api, patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client") as mock_cloud_client, + patch("src.crons.state_trackers.cloud_service") as mock_cloud_service, ): manifest = json.load(data) mock_get_manifest.return_value = manifest @@ -129,8 +129,8 @@ def test_retrieve_annotations(self): mock_storage_client = Mock() mock_storage_client.create_file = Mock() mock_storage_client.list_files = Mock(return_value=[]) - mock_cloud_client.make_client = Mock(return_value=mock_storage_client) - mock_cloud_client.S3Client = Mock(return_value=mock_storage_client) + mock_cloud_service.make_client = Mock(return_value=mock_storage_client) + mock_cloud_service.S3Client = Mock(return_value=mock_storage_client) retrieve_annotations() @@ -251,7 +251,7 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): patch("src.crons.state_trackers.cvat_api"), patch("src.crons.state_trackers.cvat_api.get_job_annotations") as mock_annotations, patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client.S3Client") as mock_S3Client, + patch("src.crons.state_trackers.cloud_service.S3Client") as mock_S3Client, ): manifest = json.load(data) mock_get_manifest.return_value = manifest diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index 29cc14e4b5..d5ac4e5b9f 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -79,7 +79,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): mock_gt_filenames.return_value = filenames mock_cloud_client = Mock() - mock_cloud_client.list_filenames.return_value = filenames + mock_cloud_client.list_files.return_value = filenames mock_make_cloud_client.return_value = mock_cloud_client process_incoming_job_launcher_webhooks() From 21aa731722c4f64a263a2871d4c817160e10aae6 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 1 Feb 2024 21:12:01 +0200 Subject: [PATCH 26/82] Add basic implementation for skeletons from boxes --- .../cvat/exchange-oracle/src/core/manifest.py | 84 +- .../src/core/tasks/skeletons_from_boxes.py | 72 ++ .../cvat/exchange-oracle/src/core/types.py | 1 + .../exchange-oracle/src/cvat/api_calls.py | 15 +- .../src/handlers/job_creation.py | 1090 +++++++++++++++-- .../exchange-oracle/src/utils/assignments.py | 3 +- 6 files changed, 1180 insertions(+), 85 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 6d6c293b09..35d7ce0c6b 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,10 +1,12 @@ from decimal import Decimal -from typing import Optional +from enum import Enum +from typing import Annotated, List, Literal, Optional, Tuple, Union from pydantic import AnyUrl, BaseModel, Field, root_validator from src.core.config import Config from src.core.types import TaskType +from src.utils.enums import BetterEnumMeta class DataInfo(BaseModel): @@ -16,16 +18,71 @@ class DataInfo(BaseModel): "A path to an archive with a set of points in COCO Keypoints format, " "which provides information about all objects on images" + boxes_url: Optional[AnyUrl] = None + "A path to an archive with a set of boxes in COCO Instances format, " + "which provides information about all objects on images" + + +class LabelType(str, Enum, metaclass=BetterEnumMeta): + plain = "plain" + skeleton = "skeleton" + class LabelInfo(BaseModel): name: str # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ + type: LabelType = LabelType.plain + + +class PlainLabelInfo(LabelInfo): + type: Literal[LabelType.plain] + + +class SkeletonLabelInfo(LabelInfo): + type: Literal[LabelType.skeleton] + + nodes: List[str] = Field(min_items=1) + """ + A list of node label names (only points are supposed to be nodes). + Example: + [ + "left hand", "torso", "right hand", "head" + ] + """ + + joints: List[Tuple[int, int]] + "A list of node adjacency, e.g. [[0, 1], [1, 2], [1, 3]]" + + @root_validator + @classmethod + def validate_type(cls, values: dict) -> dict: + if values["type"] != LabelType.skeleton: + raise ValueError(f"Label type must be {LabelType.skeleton}") + + # TODO: validate label names (empty strings, repeats) + + skeleton_name = values["name"] + nodes_count = len(values["nodes"]) + joints = values["joints"] + for joint_idx, joint in enumerate(joints): + for v in joint: + if not (0 <= v < nodes_count): + raise ValueError( + f"Skeleton '{skeleton_name}' joint #{joint_idx}: invalid value. " + f"Expected a number in the range [0; {nodes_count - 1}]" + ) + + return values + + +_Label = Annotated[Union[PlainLabelInfo, SkeletonLabelInfo], Field(discriminator="type")] + class AnnotationInfo(BaseModel): type: TaskType - labels: list[LabelInfo] + labels: list[_Label] "Label declarations with accepted annotation types" description: str = "" @@ -40,15 +97,6 @@ class AnnotationInfo(BaseModel): max_time: int = Field(default_factory=lambda: Config.core_config.default_assignment_time) "Maximum time per job (assignment) for an annotator, in seconds" - @root_validator - @classmethod - def validate_type(cls, values: dict) -> dict: - if values["type"] == TaskType.image_label_binary: - if len(values["labels"]) != 2: - raise ValueError("Binary classification requires 2 labels") - - return values - class ValidationInfo(BaseModel): min_quality: float = Field(ge=0) @@ -68,3 +116,17 @@ class TaskManifest(BaseModel): job_bounty: Decimal = Field(ge=0) "Assignment bounty, a decimal value in HMT" + + +def parse_manifest(manifest: dict) -> TaskManifest: + # Add default value for labels, if none provided. + # pydantic can't do this for tagged unions + + try: + labels = manifest["annotation"]["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", LabelType.plain) + except KeyError: + pass + + return TaskManifest.parse_obj(manifest) diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py new file mode 100644 index 0000000000..853877361a --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -0,0 +1,72 @@ +from typing import Dict, List, Sequence + +import attrs +from attrs import frozen +from datumaro.util import dump_json, parse_json + +SkeletonBboxMapping = Dict[int, int] + + +# TODO: migrate to pydantic +@frozen +class RoiInfo: + original_image_key: int + bbox_id: int + bbox_x: int + bbox_y: int + bbox_label: int + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + + +@frozen(kw_only=True) +class TileInfo: + roi_id: int + roi_x: float # top left + roi_y: float # top left + roi_w: float + roi_h: float + + +@frozen(kw_only=True) +class TilesetInfo: + id: int + label: int + w: int + h: int + tiles: List[TileInfo] + + +TilesetInfos = Sequence[TilesetInfo] + +# TilesetMap = Dict[int, TilesetInfo] + + +class TaskMetaLayout: + TILESET_MAP_FILENAME = "tileset_map.json" + TILESET_NAME_PATTERN = "tileset-{}" + + @classmethod + def make_tileset_sample_id(cls, tileset_id: int) -> str: + return cls.TILESET_NAME_PATTERN.format(tileset_id) + + +class TaskMetaSerializer: + def dump_tileset_map(tile_map: TilesetMap, filename: str): + tile_map = {str(k): attrs.asdict(v) for k, v in tile_map.items()} + return dump_json(filename, tile_map, indent=True, append_newline=True) + + def parse_tileset_map(filename: str) -> TilesetMap: + data = parse_json(filename) + return { + int(k): TilesetInfo(tiles=[TileInfo(**vv) for vv in v.pop("tiles", [])], **v) + for k, v in data.items() + } diff --git a/packages/examples/cvat/exchange-oracle/src/core/types.py b/packages/examples/cvat/exchange-oracle/src/core/types.py index 116e0dedf6..92a79a467c 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/types.py +++ b/packages/examples/cvat/exchange-oracle/src/core/types.py @@ -41,6 +41,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" + image_skeletons_from_boxes = "IMAGE_SKELETONS_FROM_BOXES" class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 9a6fd5dd21..566f9d039a 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -5,7 +5,7 @@ from enum import Enum from http import HTTPStatus from time import sleep -from typing import List, Optional, Tuple +from typing import Any, List, Optional, Tuple from cvat_sdk.api_client import ApiClient, Configuration, exceptions, models from cvat_sdk.api_client.api_client import Endpoint @@ -234,9 +234,16 @@ def create_cvat_webhook(project_id: int) -> models.WebhookRead: raise -def create_task(project_id: int, escrow_address: str) -> models.TaskRead: +def create_task( + project_id: int, escrow_address: str, *, labels: Optional[dict[str, Any]] = None +) -> models.TaskRead: logger = logging.getLogger("app") with get_api_client() as api_client: + kwargs = {} + + if labels: + kwargs["labels"] = labels + task_write_request = models.TaskWriteRequest( name=escrow_address, project_id=project_id, @@ -271,6 +278,7 @@ def put_task_data( cloudstorage_id: int, *, filenames: Optional[list[str]] = None, + job_filenames: Optional[list[list[str]]] = None, sort_images: bool = True, ) -> None: logger = logging.getLogger("app") @@ -282,6 +290,9 @@ def put_task_data( else: kwargs["filename_pattern"] = "*" + if job_filenames: + kwargs["job_file_mapping"] = job_filenames + data_request = models.DataRequest( chunk_size=Config.cvat_config.cvat_job_segment_size, cloud_storage_id=cloudstorage_id, diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index ce8c19179b..cd5da600a1 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -4,19 +4,22 @@ import random import uuid from contextlib import ExitStack -from itertools import groupby +from dataclasses import dataclass +from itertools import groupby, product from logging import Logger from math import ceil from tempfile import TemporaryDirectory -from typing import Dict, List, Sequence, Tuple, Union, cast +from typing import Dict, List, Sequence, Tuple, TypeVar, Union, cast import cv2 import datumaro as dm import numpy as np from datumaro.util import take_by +from datumaro.util.annotation_util import BboxCoords, bbox_iou from datumaro.util.image import IMAGE_EXTENSIONS, decode_image, encode_image import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.core.tasks.skeletons_from_boxes as skeletons_from_boxes_task import src.cvat.api_calls as cvat_api import src.services.cloud as cloud_service import src.services.cvat as db_service @@ -39,6 +42,7 @@ TaskType.image_points: CvatLabelType.points, TaskType.image_boxes: CvatLabelType.rectangle, TaskType.image_boxes_from_points: CvatLabelType.rectangle, + TaskType.image_skeletons_from_boxes: CvatLabelType.points, } DM_DATASET_FORMAT_MAPPING = { @@ -46,6 +50,7 @@ TaskType.image_points: "coco_person_keypoints", TaskType.image_boxes: "coco_instances", TaskType.image_boxes_from_points: "coco_instances", + TaskType.image_skeletons_from_boxes: "coco_person_keypoints", } DM_GT_DATASET_FORMAT_MAPPING = { @@ -54,6 +59,7 @@ TaskType.image_points: "coco_instances", TaskType.image_boxes: "coco_instances", TaskType.image_boxes_from_points: "coco_instances", + TaskType.image_skeletons_from_boxes: "coco_person_keypoints", } CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { @@ -82,13 +88,20 @@ class InvalidImageInfo(DatasetValidationError): pass -class BoxesFromPointsTaskBuilder: - class _NotConfigured: - def __bool__(self) -> bool: - return False +T = TypeVar("T") + + +class _Undefined: + def __bool__(self) -> bool: + return False + - _not_configured = _NotConfigured() +_unset = _Undefined() +_MaybeUnset = Union[T, _Undefined] + + +class BoxesFromPointsTaskBuilder: def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.exit_stack = ExitStack() self.manifest = manifest @@ -97,38 +110,29 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.logger: Logger = NullLogger() - self.input_gt_data: Union[bytes, self._NotConfigured] = self._not_configured - self.input_points_data: Union[bytes, self._NotConfigured] = self._not_configured + self.input_gt_data: _MaybeUnset[bytes] = _unset + self.input_points_data: _MaybeUnset[bytes] = _unset # Computed values - self.input_filenames: Union[self._NotConfigured, Sequence[str]] = self._not_configured - self.input_gt_dataset: Union[self._NotConfigured, dm.Dataset] = self._not_configured - self.input_points_dataset: Union[self._NotConfigured, dm.Dataset] = self._not_configured + self.input_filenames: _MaybeUnset[Sequence[str]] = _unset + self.input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self.input_points_dataset: _MaybeUnset[dm.Dataset] = _unset - # Output values - self.gt_dataset: Union[dm.Dataset, self._NotConfigured] = self._not_configured + self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset - self.bbox_point_mapping: Union[ - boxes_from_points_task.BboxPointMapping, self._NotConfigured - ] = self._not_configured + self.bbox_point_mapping: _MaybeUnset[boxes_from_points_task.BboxPointMapping] = _unset "bbox_id -> point_id" - self.roi_size_estimations: Union[ - Dict[int, Tuple[float, float]], self._NotConfigured - ] = self._not_configured + self.roi_size_estimations: _MaybeUnset[Dict[int, Tuple[float, float]]] = _unset "label_id -> (rel. w, rel. h)" - self.rois: Union[ - boxes_from_points_task.RoiInfos, self._NotConfigured - ] = self._not_configured - self.roi_filenames: Union[ - boxes_from_points_task.RoiFilenames, self._NotConfigured - ] = self._not_configured + self.rois: _MaybeUnset[boxes_from_points_task.RoiInfos] = _unset + self.roi_filenames: _MaybeUnset[boxes_from_points_task.RoiFilenames] = _unset - self.job_layout: Union[Sequence[Sequence[str]], self._NotConfigured] = self._not_configured + self.job_layout: _MaybeUnset[Sequence[Sequence[str]]] = _unset "File lists per CVAT job" - self.label_configuration: Union[Sequence[dict], self._NotConfigured] = self._not_configured + self.label_configuration: _MaybeUnset[Sequence[dict]] = _unset # Configuration / constants # TODO: consider WebP if produced files are too big @@ -213,7 +217,7 @@ def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm return dm.Dataset.import_from(annotation_filename, format=dataset_format) def _parse_gt(self): - assert self.input_gt_data is not self._not_configured + assert self.input_gt_data is not _unset self.input_gt_dataset = self._parse_dataset( self.input_gt_data, @@ -221,7 +225,7 @@ def _parse_gt(self): ) def _parse_points(self): - assert self.input_points_data is not self._not_configured + assert self.input_points_data is not _unset self.input_points_dataset = self._parse_dataset( self.input_points_data, dataset_format=self.points_format @@ -268,11 +272,12 @@ def _validate_gt_filenames(self): ) def _validate_gt(self): - assert self.input_filenames is not self._not_configured - assert self.input_gt_dataset is not self._not_configured + assert self.input_filenames is not _unset + assert self.input_gt_dataset is not _unset self._validate_gt_filenames() self._validate_gt_labels() + # TODO: add gt annotation validation, keep track of excluded annotations def _format_list( self, items: Sequence[str], *, max_items: int = None, separator: str = ", " @@ -322,24 +327,7 @@ def _validate_points_categories(self): self.input_points_dataset.init_cache() def _validate_points_filenames(self): - points_filenames = set() - filenames_with_invalid_points = set() - for sample in self.input_points_dataset: - sample_id = sample.id + sample.media.ext - points_filenames.add(sample_id) - - skeletons = [a for a in sample.annotations if isinstance(a, dm.Skeleton)] - for skeleton in skeletons: - if len(skeleton.elements) != 1: - filenames_with_invalid_points.add(sample_id) - break - - if filenames_with_invalid_points: - raise MismatchingAnnotations( - "Some images have invalid points: {}".format( - self._format_list(filenames_with_invalid_points) - ) - ) + points_filenames = set(sample.id + sample.media.ext for sample in self.input_points_dataset) known_data_filenames = set(self.input_filenames) matched_points_filenames = points_filenames.intersection(known_data_filenames) @@ -368,6 +356,7 @@ def _validate_points_annotations(self): excluded_samples = [] for sample in self.input_points_dataset: + # Could fail on this as well image_h, image_w = sample.image.size for skeleton in sample.annotations: @@ -375,6 +364,19 @@ def _validate_points_annotations(self): if not isinstance(skeleton, dm.Skeleton): continue + if len(skeleton.elements) != 1: + message = ( + "Sample '{}': skeleton #{} ({}) skipped - " + "invalid points count ({}), expected 1".format( + sample.id, + skeleton.id, + label_cat[skeleton.label].name, + len(skeleton.elements), + ) + ) + excluded_samples.append(((sample.id, sample.subset), message)) + break + point = skeleton.elements[0] px, py = point.points[:2] @@ -405,8 +407,8 @@ def _validate_points_annotations(self): ) def _validate_points(self): - assert self.input_filenames is not self._not_configured - assert self.input_points_dataset is not self._not_configured + assert self.input_filenames is not _unset + assert self.input_points_dataset is not _unset self._validate_points_categories() self._validate_points_filenames() @@ -417,9 +419,9 @@ def _is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) def _prepare_gt(self): - assert self.input_filenames is not self._not_configured - assert self.input_points_dataset is not self._not_configured - assert self.input_gt_dataset is not self._not_configured + assert self.input_filenames is not _unset + assert self.input_points_dataset is not _unset + assert self.input_gt_dataset is not _unset assert [ label.name for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] ] == [label.name for label in self.manifest.annotation.labels] @@ -437,7 +439,7 @@ def _prepare_gt(self): excluded_boxes_messages = [] total_boxes = 0 - gt_per_class = {} + gt_count_per_class = {} bbox_point_mapping = {} # bbox id -> point id for gt_sample in self.input_gt_dataset: @@ -449,7 +451,7 @@ def _prepare_gt(self): gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] - # Samples without boxes are allowed + # Samples without boxes are allowed, so we just skip them without an error if not gt_boxes: continue @@ -525,6 +527,8 @@ def _prepare_gt(self): ) continue + gt_count_per_class[gt_bbox.label] = gt_count_per_class.get(gt_bbox.label, 0) + 1 + matched_boxes.append(gt_bbox) bbox_point_mapping[gt_bbox_id] = matched_skeletons[0].id @@ -545,7 +549,7 @@ def _prepare_gt(self): gt_labels_without_anns = [ gt_label_cat[label_id] - for label_id, label_count in gt_per_class.items() + for label_id, label_count in gt_count_per_class.items() if not label_count ] if gt_labels_without_anns: @@ -559,7 +563,7 @@ def _prepare_gt(self): self.bbox_point_mapping = bbox_point_mapping def _estimate_roi_sizes(self): - assert self.gt_dataset is not self._not_configured + assert self.gt_dataset is not _unset assert [label.name for label in self.gt_dataset.categories()[dm.AnnotationType.label]] == [ label.name for label in self.manifest.annotation.labels ] @@ -607,9 +611,9 @@ def _estimate_roi_sizes(self): self.roi_size_estimations = roi_size_estimations_per_label def _prepare_roi_info(self): - assert self.gt_dataset is not self._not_configured - assert self.roi_size_estimations is not self._not_configured - assert self.input_points_dataset is not self._not_configured + assert self.gt_dataset is not _unset + assert self.roi_size_estimations is not _unset + assert self.input_points_dataset is not _unset rois: List[boxes_from_points_task.RoiInfo] = [] for sample in self.input_points_dataset: @@ -659,7 +663,7 @@ def _mangle_filenames(self): Mangle filenames in the dataset to make them less recognizable by annotators and hide private dataset info """ - assert self.rois is not self._not_configured + assert self.rois is not _unset # TODO: maybe add different names for the same GT images in # different jobs to make them even less recognizable @@ -671,8 +675,8 @@ def _prepare_job_layout(self): # Make job layouts wrt. manifest params # 1 job per task as CVAT can't repeat images in jobs, but GTs can repeat in the dataset - assert self.rois is not self._not_configured - assert self.bbox_point_mapping is not self._not_configured + assert self.rois is not _unset + assert self.bbox_point_mapping is not _unset gt_point_ids = set(self.bbox_point_mapping.values()) gt_filenames = [self.roi_filenames[point_id] for point_id in gt_point_ids] @@ -761,10 +765,10 @@ def _extract_and_upload_rois(self): # TODO: maybe optimize via splitting into separate threads (downloading, uploading, processing) # Watch for the memory used, as the whole dataset can be quite big (gigabytes, terabytes) # Consider also packing RoIs cut into archives - assert self.input_points_dataset is not self._not_configured - assert self.rois is not self._not_configured - assert self.input_filenames is not self._not_configured - assert self.roi_filenames is not self._not_configured + assert self.input_points_dataset is not _unset + assert self.rois is not _unset + assert self.input_filenames is not _unset + assert self.roi_filenames is not _unset src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) src_prefix = "" @@ -827,8 +831,8 @@ def _extract_and_upload_rois(self): ) def _create_on_cvat(self): - assert self.job_layout is not self._not_configured - assert self.label_configuration is not self._not_configured + assert self.job_layout is not _unset + assert self.label_configuration is not _unset input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) oracle_bucket = self.oracle_data_bucket @@ -924,6 +928,945 @@ def build(self): self._create_on_cvat() +class SkeletonsFromBoxesTaskBuilder: + @dataclass + class _JobParams: + label_id: int + roi_ids: List[int] + tileset_ids: _MaybeUnset[List[int]] = _unset + + @dataclass + class _TilesetParams: + roi_ids: List[int] + + all_tile_coords: np.ndarray + "N x (x, y, w, h)" + + all_roi_coords: np.ndarray + "N x (x, y, w, h)" + + frame_size: np.ndarray + "h, w" + + roi_border_size: np.ndarray + "h, w" + + tile_margin_size: np.ndarray + "h, w" + + def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): + self.exit_stack = ExitStack() + self.manifest = manifest + self.escrow_address = escrow_address + self.chain_id = chain_id + + self.logger: Logger = NullLogger() + + self.input_gt_data: _MaybeUnset[bytes] = _unset + self.input_boxes_data: _MaybeUnset[bytes] = _unset + + # Computed values + self.input_filenames: _MaybeUnset[Sequence[str]] = _unset + self.input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self.input_boxes_dataset: _MaybeUnset[dm.Dataset] = _unset + + self.tileset_filenames: _MaybeUnset[Dict[int, str]] = _unset + self.job_params: _MaybeUnset[List[self._JobParams]] = _unset + self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset + + # Configuration / constants + # TODO: consider WebP if produced files are too big + self.tileset_file_ext = ".png" # supposed to be lossless and reasonably compressing + "File extension for tileset images, with leading dot (.) included" + + self.list_display_threshold = 5 + "The maximum number of rendered list items in a message" + + self.roi_size_mult = 1.1 + "Additional point ROI size multiplier" + + self.boxes_format = "coco_instances" + + self.embed_bbox_in_roi_image = True + "Put a bbox into the extracted skeleton RoI images" + + self.embed_tile_border = True + + self.roi_embedded_bbox_color = (0, 255, 255) # BGR + + self.tileset_size = (3, 2) # W, H + self.tileset_background_color = (245, 240, 242) # BGR - CVAT background color + self.tile_border_color = (255, 255, 255) + + self.oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + self.min_label_gt_samples = 5 + + self.max_discarded_threshold = 0.5 + """ + The maximum allowed percent of discarded + GT annotations or samples for successful job launch + """ + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + self.close() + + def close(self): + self.exit_stack.close() + + def set_logger(self, logger: Logger): + # TODO: add escrow info into messages + self.logger = logger + return self + + def _download_input_data(self): + data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + boxes_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.boxes_url) + + data_storage_client = self._make_cloud_storage_client(data_bucket) + gt_storage_client = self._make_cloud_storage_client(gt_bucket) + boxes_storage_client = self._make_cloud_storage_client(boxes_bucket) + + data_filenames = data_storage_client.list_filenames( + data_bucket.url.bucket_name, + prefix=data_bucket.url.path, + ) + self.input_filenames = filter_image_files(data_filenames) + + self.input_gt_data = gt_storage_client.download_file( + gt_bucket.url.bucket_name, + gt_bucket.url.path, + ) + + self.input_boxes_data = boxes_storage_client.download_file( + boxes_bucket.url.bucket_name, + boxes_bucket.url.path, + ) + + def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: + temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) + + annotation_filename = os.path.join(temp_dir, "annotations.json") + with open(annotation_filename, "wb") as f: + f.write(annotation_file_data) + + return dm.Dataset.import_from(annotation_filename, format=dataset_format) + + def _parse_gt(self): + assert self.input_gt_data is not _unset + + self.input_gt_dataset = self._parse_dataset( + self.input_gt_data, + dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], + ) + + def _parse_boxes(self): + assert self.input_boxes_data is not _unset + + self.input_boxes_dataset = self._parse_dataset( + self.input_boxes_data, dataset_format=self.boxes_format + ) + + def _validate_gt_labels(self): + gt_labels = set( + (label.name, label.parent) + for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + ) + + manifest_labels = set() + for skeleton_label in self.manifest.annotation.labels: + manifest_labels.add((skeleton_label.name, "")) + for node_label in skeleton_label.nodes: + manifest_labels.add((skeleton_label.name, node_label)) + + if gt_labels - manifest_labels: + raise DatasetValidationError( + "GT labels do not match job labels. Unknown labels: {}".format( + self._format_list( + label_name if not parent_name else f"{parent_name}.{label_name}" + for label_name, parent_name in gt_labels - manifest_labels + ), + ) + ) + + # Reorder labels to match manifest + self.input_gt_dataset.transform( + "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self.input_gt_dataset.init_cache() + + def _validate_gt_filenames(self): + gt_filenames = set(s.id + s.media.ext for s in self.input_gt_dataset) + + known_data_filenames = set(self.input_filenames) + matched_gt_filenames = gt_filenames.intersection(known_data_filenames) + + if len(gt_filenames) != len(matched_gt_filenames): + extra_gt = list(map(os.path.basename, gt_filenames - matched_gt_filenames)) + + raise MismatchingAnnotations( + "Failed to find several validation samples in the dataset files: {}".format( + self._format_list(extra_gt) + ) + ) + + if len(gt_filenames) < self.manifest.validation.val_size: + raise TooFewSamples( + f"Too few validation samples provided ({len(gt_filenames)}), " + f"at least {self.manifest.validation.val_size} required." + ) + + def _validate_gt(self): + assert self.input_filenames is not _unset + assert self.input_gt_dataset is not _unset + + self._validate_gt_filenames() + self._validate_gt_labels() + + # TODO: check coordinates + # TODO: check there are matching pairs of bbox/skeleton in GT + # TODO: pass discarded/total down + + def _validate_boxes_categories(self): + boxes_dataset_categories = self.input_boxes_dataset.categories() + boxes_dataset_label_cat: dm.LabelCategories = boxes_dataset_categories[ + dm.AnnotationType.label + ] + + boxes_labels = set(label.name for label in boxes_dataset_label_cat if not label.parent) + manifest_labels = set(label.name for label in self.manifest.annotation.labels) + if manifest_labels != boxes_labels: + raise DatasetValidationError("Bbox labels do not match job labels") + + # Reorder labels to match manifest + self.input_boxes_dataset.transform( + "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ) + self.input_boxes_dataset.init_cache() + + def _validate_boxes_filenames(self): + boxes_filenames = set(sample.id + sample.media.ext for sample in self.input_boxes_dataset) + + known_data_filenames = set(self.input_filenames) + matched_boxes_filenames = boxes_filenames.intersection(known_data_filenames) + + if len(known_data_filenames) != len(matched_boxes_filenames): + missing_box_samples = list( + map(os.path.basename, known_data_filenames - matched_boxes_filenames) + ) + extra_point_samples = list( + map(os.path.basename, boxes_filenames - matched_boxes_filenames) + ) + + raise MismatchingAnnotations( + "Mismatching bbox info and input files: {}".format( + "; ".join( + "{} missing boxes".format(self._format_list(missing_box_samples)), + "{} extra boxes".format(self._format_list(extra_point_samples)), + ) + ) + ) + + def _validate_boxes_annotations(self): + label_cat: dm.LabelCategories = self.input_boxes_dataset.categories()[ + dm.AnnotationType.label + ] + + # TODO: check for excluded boxes count + excluded_samples = [] + for sample in self.input_boxes_dataset: + # Could fail on this as well + image_h, image_w = sample.image.size + + for bbox in sample.annotations: + # Could fail on this as well + if not isinstance(bbox, dm.Bbox): + continue + + if not ( + (0 <= bbox.x < bbox.x + bbox.w < image_w) + and (0 <= bbox.y < bbox.y + bbox.h < image_h) + ): + message = "Sample '{}': bbox #{} ({}) skipped - " "invalid coordinates".format( + sample.id, bbox.id, label_cat[bbox.label].name + ) + excluded_samples.append(((sample.id, sample.subset), message)) + + if len(excluded_samples) > len(self.input_boxes_dataset) * self.max_discarded_threshold: + raise DatasetValidationError( + "Too many samples discarded, canceling job creation. Errors: {}".format( + self._format_list([message for _, message in excluded_samples]) + ) + ) + + for excluded_sample, _ in excluded_samples: + self.input_boxes_dataset.remove(*excluded_sample) + + if excluded_samples: + self.logger.info( + "Some samples were excluded due to errors found: {}".format( + self._format_list([m for _, m in excluded_samples], separator="\n") + ) + ) + + def _validate_boxes(self): + assert self.input_filenames is not _unset + assert self.input_boxes_dataset is not _unset + + self._validate_boxes_categories() + self._validate_boxes_filenames() + self._validate_boxes_annotations() + + def _format_list( + self, items: Sequence[str], *, max_items: int = None, separator: str = ", " + ) -> str: + if max_items is None: + max_items = self.list_display_threshold + + remainder_count = len(items) - max_items + return "{}{}".format( + separator.join(items[:max_items]), + f"(and {remainder_count} more)" if remainder_count > 0 else "", + ) + + def _match_boxes(self, a: BboxCoords, b: BboxCoords): + return bbox_iou(a, b) > 0 + + def _prepare_gt(self): + assert self.input_filenames is not _unset + assert self.input_boxes_dataset is not _unset + assert self.input_gt_dataset is not _unset + assert [ + label.name + for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + assert [ + label.name + for label in self.input_boxes_dataset.categories()[dm.AnnotationType.label] + if not label.parent + ] == [label.name for label in self.manifest.annotation.labels] + + gt_dataset = dm.Dataset(categories=self.input_gt_dataset.categories(), media_type=dm.Image) + + gt_label_cat: dm.LabelCategories = self.input_gt_dataset.categories()[ + dm.AnnotationType.label + ] + + excluded_skeletons_messages = [] + total_skeletons = 0 + gt_count_per_class = {} + + skeleton_bbox_mapping = {} # skeleton id -> bbox id + for gt_sample in self.input_gt_dataset: + boxes_sample = self.input_boxes_dataset.get(gt_sample.id, gt_sample.subset) + assert boxes_sample + + gt_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] + input_boxes = [a for a in boxes_sample.annotations if isinstance(a, dm.Bbox)] + + # Samples without boxes are allowed, so we just skip them without an error + if not gt_skeletons: + continue + + total_skeletons += len(gt_skeletons) + + # Find unambiguous gt skeleton - input bbox pairs + matched_skeletons = [] + visited_skeletons = set() + for gt_skeleton in gt_skeletons: + gt_skeleton_id = gt_skeleton.id + + if len(visited_skeletons) == len(gt_skeletons): + # Handle unmatched boxes + excluded_skeletons_messages.append( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "no matching boxes found".format( + gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name + ) + ) + continue + + matched_boxes: List[dm.Bbox] = [] + for input_bbox in input_boxes: + skeleton_id = input_bbox.id + if skeleton_id in visited_skeletons: + continue + + gt_skeleton_bbox = gt_skeleton.get_bbox() + if not self._match_boxes(input_bbox.get_bbox(), gt_skeleton_bbox): + continue + + if input_bbox.label != gt_skeleton.label: + continue + + matched_boxes.append(input_bbox) + visited_skeletons.add(skeleton_id) + + if len(matched_boxes) > 1: + # Handle ambiguous matches + excluded_skeletons_messages.append( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "too many matching boxes ({}) found".format( + gt_sample.id, + gt_skeleton_id, + gt_label_cat[gt_skeleton.label].name, + len(matched_boxes), + ) + ) + continue + elif len(matched_boxes) == 0: + # Handle unmatched boxes + excluded_skeletons_messages.append( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "no matching boxes found".format( + gt_sample.id, + gt_skeleton_id, + gt_label_cat[gt_skeleton.label].name, + ) + ) + continue + + # TODO: maybe check if the top match is good enough + # (high thresholds may lead to matching issues for small objects) + + gt_count_per_class[gt_skeleton.label] = ( + gt_count_per_class.get(gt_skeleton.label, 0) + 1 + ) + + matched_skeletons.append(gt_skeleton) + skeleton_bbox_mapping[gt_skeleton_id] = matched_boxes[0].id + + if not matched_skeletons: + continue + + gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) + + if len(skeleton_bbox_mapping) < (1 - self.max_discarded_threshold) * total_skeletons: + raise DatasetValidationError( + "Too many GT skeletons discarded ({} out of {}). " + "Please make sure each GT skeleton matches exactly 1 bbox".format( + total_skeletons - len(skeleton_bbox_mapping), total_skeletons + ) + ) + elif excluded_skeletons_messages: + self.logger.info(self._format_list(excluded_skeletons_messages, separator="\n")) + + labels_with_few_gt = [ + gt_label_cat[label_id] + for label_id, label_count in gt_count_per_class.items() + if label_count < self.min_label_gt_samples + ] + if labels_with_few_gt: + raise DatasetValidationError( + "No matching GT boxes/points annotations found for some classes: {}".format( + self._format_list(labels_with_few_gt) + ) + ) + + self.gt_dataset = gt_dataset + self.skeleton_bbox_mapping = skeleton_bbox_mapping + + def _prepare_roi_infos(self): + assert self.gt_dataset is not _unset + assert self.input_boxes_dataset is not _unset + + rois: List[skeletons_from_boxes_task.RoiInfo] = [] + for sample in self.input_boxes_dataset: + for bbox in sample.annotations: + if not isinstance(bbox, dm.Bbox): + continue + + original_bbox_x = int(bbox.x) + original_bbox_y = int(bbox.y) + + image_h, image_w = sample.image.size + + roi_margin_w = bbox.w * self.roi_size_mult + roi_margin_h = bbox.h * self.roi_size_mult + + roi_left = max(0, original_bbox_x - int(roi_margin_w / 2)) + roi_top = max(0, original_bbox_y - int(roi_margin_h / 2)) + roi_right = min(image_w, original_bbox_x + ceil(roi_margin_w / 2)) + roi_bottom = min(image_h, original_bbox_y + ceil(roi_margin_h / 2)) + + roi_w = roi_right - roi_left + roi_h = roi_bottom - roi_top + + new_bbox_x = original_bbox_x - roi_left + new_bbox_y = original_bbox_y - roi_top + + rois.append( + skeletons_from_boxes_task.RoiInfo( + original_image_key=sample.attributes["id"], + bbox_id=bbox.id, + bbox_label=bbox.label, + bbox_x=new_bbox_x, + bbox_y=new_bbox_y, + roi_x=roi_left, + roi_y=roi_top, + roi_w=roi_w, + roi_h=roi_h, + ) + ) + + self.roi_infos = rois + + def _prepare_dataset_tileset_params(self): + assert self.gt_dataset is not _unset + assert self.input_boxes_dataset is not _unset + assert self.roi_infos is not _unset + assert self.job_params is not _unset + + combined_tileset_params: Dict[int, self._TilesetParams] = {} + self.tileset_params = combined_tileset_params + roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self.roi_infos} + + for job_params in self.job_params: + assert job_params.tileset_ids is _unset + tileset_ids = [] + for tileset_roi_ids in take_by(job_params.roi_ids, np.prod(self.tileset_size)): + tileset_id = len(combined_tileset_params) + combined_tileset_params[tileset_id] = self._prepare_tileset_params( + [roi_info_by_id[roi_id] for roi_id in tileset_roi_ids], + grid_size=self.tileset_size, + ) + tileset_ids.append(tileset_id) + + job_params.tileset_ids = tileset_ids + + self.tileset_params = combined_tileset_params + + @classmethod + def _prepare_tileset_params( + cls, + rois: skeletons_from_boxes_task.RoiInfos, + *, + grid_size: Tuple[int, int], + ) -> _TilesetParams: + """ + RoI is inside tile, centered + border is for RoI + border is inside tile or margin + margin is for tile + margin size is shared between 2 adjacent tiles + + A regular row/col looks like (b - border, m - margin, T - tile): + b|RoI1|b|tile - RoI1 - b|m - 2 * b|tile - RoI2 - b|b|RoI2|b + + A tileset looks like this: + bbbbbbb + bTmTmTb + bmmmmmb + bTmTmTb + bbbbbbb + """ + + assert len(rois) <= np.prod(grid_size) + + # FIXME: RoI size is not limited by size, it can lead to highly uneven tiles + base_tile_size = np.max([(sample.roi_h, sample.roi_w) for sample in rois], axis=0).astype( + int + ) + + grid_size = np.array(grid_size, dtype=int) + roi_border = np.array((2, 2), dtype=int) + tile_margin = np.array((20, 20), dtype=int) + assert np.all(2 * roi_border < tile_margin) + + frame_size = ( + base_tile_size * grid_size[::-1] + tile_margin * (grid_size[::-1] - 1) + 2 * roi_border + ) + + all_tile_coords = np.zeros((np.prod(grid_size), 4), dtype=float) # x, y, w, h + for grid_id, grid_pos in enumerate(product(range(grid_size[1]), range(grid_size[0]))): + grid_pos = np.array(grid_pos[::-1], dtype=int) + tile_coords = all_tile_coords[grid_id] + tile_coords[:2] = ( + base_tile_size[::-1] * grid_pos + + tile_margin / 2 * grid_pos + + tile_margin / 2 * np.clip(grid_pos - 1, 0, None) + + roi_border * (grid_pos > 0) + ) + tile_coords[2:] = ( + base_tile_size[::-1] + + tile_margin / 2 + + np.where( + (grid_pos == 0) | (grid_pos == (grid_size - 1)), + roi_border, + tile_margin / 2, + ) + ) + + all_roi_coords = np.zeros((np.prod(grid_size), 4), dtype=int) # x, y, w, h + for grid_id, roi_info in enumerate(rois): + tile_coords = all_tile_coords[grid_id] + roi_coords = all_roi_coords[grid_id] + + roi_size = np.array((roi_info.roi_h, roi_info.roi_w), dtype=int) + roi_offset = tile_coords[2:] / 2 - roi_size[::-1] / 2 + roi_coords[:] = [*(tile_coords[:2] + roi_offset), *roi_size[::-1]] # (x, y, w, h) + + return cls._TilesetParams( + roi_ids=[roi.bbox_id for roi in rois], + all_roi_coords=all_roi_coords, + all_tile_coords=all_tile_coords, + grid_size=grid_size, + frame_size=frame_size[::-1], + roi_border_size=roi_border, + tile_margin=tile_margin, + ) + + def _mangle_filenames(self): + """ + Mangle filenames in the dataset to make them less recognizable by annotators + and hide private dataset info + """ + assert self.tileset_params is not _unset + + # TODO: maybe add different names for the same GT images in + # different jobs to make them even less recognizable + self.tileset_filenames = { + tileset_id: str(uuid.uuid4()) + self.tileset_file_ext + for tileset_id in self.tileset_params.keys() + } + + def _prepare_job_params(self): + assert self.roi_infos is not _unset + assert self.skeleton_bbox_mapping is not _unset + + # Make job layouts wrt. manifest params + # 1 job per task, 1 task for each point label + # + # Unlike other task types, here we use a grid of RoIs, + # so the absolute job size numbers from manifest are multiplied by the grid size. + # Then, we add a percent of job tiles for validation, keeping the requested ratio. + gt_percent = self.manifest.validation.val_size / (self.manifest.annotation.job_size or 1) + + job_params: List[self._JobParams] = [] + + _roi_key_label = lambda roi_info: roi_info.bbox_label + rois_by_label = { + label: list(g) + for label, g in groupby(sorted(self.roi_infos, key=_roi_key_label), key=_roi_key_label) + } + + roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self.roi_infos} + + tileset_size = np.prod(self.tileset_size) + for label, label_rois in rois_by_label.items(): + # FIXME: RoI sizes are not limited, so they can occupy up to the whole image. + # Sort by frame size to make tile sizes more aligned. + # This doesn't totally solve the problem, but makes things better. + label_rois = sorted(label_rois, key=lambda roi_info: roi_info.roi_w * roi_info.roi_h) + + label_gt_roi_ids = set( + roi_id + for roi_id in self.skeleton_bbox_mapping.values() + if roi_info_by_id[roi_id].bbox_label == label + ) + + label_data_roi_ids = [ + roi_info.bbox_id + for roi_info in self.roi_infos + if roi_info.bbox_id not in label_gt_roi_ids + ] + # Can't really shuffle if we sort by size + # TODO: maybe shuffle within bins by size + # random.shuffle(label_data_roi_ids) + + for job_data_roi_ids in take_by( + label_data_roi_ids, tileset_size * self.manifest.annotation.job_size + ): + job_gt_count = max( + self.manifest.validation.val_size, int(gt_percent * len(job_data_roi_ids)) + ) + + # TODO: maybe use size bins and take from them to match data sizes + job_gt_roi_ids = random.sample(label_gt_roi_ids, k=job_gt_count) + + job_roi_ids = list(job_data_roi_ids) + list(job_gt_roi_ids) + random.shuffle(job_roi_ids) + + job_params.append(self._JobParams(label_id=label, roi_ids=job_roi_ids)) + + self.job_params = job_params + + def _upload_task_meta(self): + raise NotImplementedError + + def _create_tileset_frame( + self, + tileset_params: _TilesetParams, + roi_images: dict[int, np.ndarray], + ) -> np.ndarray: + frame = np.zeros((*tileset_params.frame_size, 3), dtype=np.uint8) + frame[:, :] = self.tileset_background_color + + roi_border = tileset_params.roi_border_size + border_color = self.tile_border_color + + for grid_pos, roi_id in enumerate(tileset_params.roi_ids): + roi_coords = tileset_params.all_roi_coords[grid_pos].astype(int) + roi_image = roi_images[roi_id] + + if self.embed_tile_border: + tile_coords = tileset_params.all_tile_coords[grid_pos].astype(int) + frame[ + tile_coords[1] : tile_coords[1] + tile_coords[3], + tile_coords[0] : tile_coords[0] + tile_coords[2], + ] = 0 + + frame[ + tile_coords[1] + 1 : tile_coords[1] + tile_coords[3] - 2, + tile_coords[0] + 1 : tile_coords[0] + tile_coords[2] - 2, + ] = self.tileset_background_color + + border_coords = np.array( + [*(roi_coords[:2] - roi_border), *(roi_coords[2:] + 2 * roi_border)], + dtype=int, + ) + frame[ + border_coords[1] : border_coords[1] + border_coords[3], + border_coords[0] : border_coords[0] + border_coords[2], + ] = border_color + + frame[ + roi_coords[1] : roi_coords[1] + roi_coords[3], + roi_coords[0] : roi_coords[0] + roi_coords[2], + ] = roi_image[ + : roi_coords[3], : roi_coords[2] + ] # in some cases size can be truncated on previous iterations + + return frame + + def _draw_roi_bbox(self, roi_image: np.ndarray, bbox: dm.Bbox) -> np.ndarray: + return cv2.rectangle( + roi_image, + tuple(map(int, (bbox.x, bbox.y))), + tuple(map(int, (bbox.x + bbox.w, bbox.y + bbox.h))), + self.roi_embedded_bbox_color, + 2, # TODO: maybe improve line thickness + cv2.LINE_4, + ) + + def _create_and_upload_tilesets(self): + assert self.tileset_filenames is not _unset + assert self.tileset_infos is not _unset + + # TODO: optimize downloading, this implementation won't work for big datasets + src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + src_prefix = "" + dst_bucket = self.oracle_data_bucket + + src_client = self._make_cloud_storage_client(src_bucket) + dst_client = self._make_cloud_storage_client(dst_bucket) + + image_id_to_filename = { + sample.attributes["id"]: sample.image.path for sample in self.input_points_dataset + } + + filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} + + _roi_info_key = lambda e: e.original_image_key + roi_info_by_image: Dict[str, Sequence[skeletons_from_boxes_task.RoiInfo]] = { + image_id_to_filename[image_id]: list(g) + for image_id, g in groupby(sorted(self.roi_infos, key=_roi_info_key), key=_roi_info_key) + } + + bbox_by_id = { + bbox.id: bbox + for sample in self.input_boxes_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + roi_images = {} + for filename in self.input_filenames: + image_roi_infos = roi_info_by_image.get(filename, []) + if not image_roi_infos: + continue + + image_bytes = src_client.download_file( + src_bucket.url.bucket_name, os.path.join(src_prefix, filename) + ) + image_pixels = decode_image(image_bytes) + + sample = filename_to_sample[filename] + if tuple(sample.image.size) != tuple(image_pixels.shape[:2]): + # TODO: maybe rois should be regenerated instead + # Option 2: accumulate errors, fail when some threshold is reached + # Option 3: add special handling for cases when image is only rotated (exif etc.) + raise InvalidImageInfo( + f"Sample '{filename}': invalid size provided in the point annotations" + ) + + for roi_info in image_roi_infos: + roi_pixels = image_pixels[ + roi_info.roi_y : roi_info.roi_y + roi_info.roi_h, + roi_info.roi_x : roi_info.roi_x + roi_info.roi_w, + ] + + if self.embed_bbox_in_roi_image: + roi_pixels = self._draw_roi_bbox(roi_pixels, bbox_by_id[roi_info.bbox_id]) + + roi_images[roi_info.bbox_id] = roi_pixels + + for tileset_id, tileset_params in self.tileset_params.items(): + tileset_pixels = self._create_tileset_frame( + tileset_params, roi_images=[roi_images[roi_id] for roi_id in tileset_params.roi_ids] + ) + + filename = self.tileset_filenames[tileset_id] + tileset_bytes = encode_image(tileset_pixels, os.path.splitext(filename)[-1]) + + dst_client.create_file( + dst_bucket.url.bucket_name, + filename=compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + data=tileset_bytes, + ) + + def _create_on_cvat(self): + assert self.job_params is not _unset + + input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) + oracle_bucket = self.oracle_data_bucket + + # Register cloud storage on CVAT to pass user dataset + cloud_storage = cvat_api.create_cloudstorage( + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[oracle_bucket.provider], + oracle_bucket.url.host_url.replace( + # TODO: remove mock + "127.0.0.1", + "172.22.0.1", + ), + oracle_bucket.url.bucket_name, + # TODO: add + # credentials=... + ) + + # Create a project + project = cvat_api.create_project( + self.escrow_address, + user_guide=self.manifest.annotation.user_guide, + # TODO: improve guide handling - split for different points + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + input_data_bucket = parse_bucket_url(self.manifest.data.data_url) + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.bucket_name, + bucket_host=input_data_bucket.host_url, + provider=input_data_bucket.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images( + session, + project.id, + [ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in self.tileset_filenames.values() + ], + ) + + _job_params_label_key = lambda ts: ts.label_id + jobs_by_label = { + label_id: list(g) + for label_id, g in groupby( + sorted(self.job_params, key=_job_params_label_key), key=_job_params_label_key + ) + } + + job_label_specs = { + skeleton_label_id: [ + { + "name": point_name + if len(self.manifest.annotation.labels) == 1 + else f"{skeleton_label}.{point_name}", + "type": "points", + } + for point_name in skeleton_label.nodes + ] + for skeleton_label_id, skeleton_label in enumerate(self.manifest.annotation.labels) + } + for skeleton_label_id, skeleton_label_jobs in jobs_by_label.items(): + job_filenames_map = [] + for job_filenames_map in skeleton_label_jobs: + job_filenames_map.append( + [ + compose_data_bucket_filename( + self.escrow_address, self.chain_id, self.tileset_filenames[tileset_id] + ) + for tileset_id in skeleton_label_jobs.tileset_ids + ] + ) + + skeleton_label_specs = job_label_specs[skeleton_label_id] + for label_spec in skeleton_label_specs: + task = cvat_api.create_task(project.id, self.escrow_address, labels=[label_spec]) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=[ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for job_filenames in job_filenames_map + for fn in job_filenames + ], + sort_images=False, + job_filenames=job_filenames_map, + ) + + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) + + @classmethod + def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: + return cloud_service.make_client(bucket_info) + + def build(self): + self._download_input_data() + self._parse_gt() + self._parse_boxes() + self._validate_gt() + self._validate_boxes() + + # Task configuration creation + self._prepare_gt() + self._prepare_roi_infos() + self._mangle_filenames() + self._prepare_job_params() + + # Data preparation + self._create_and_upload_tilesets() + self._upload_task_meta() + + self._create_on_cvat() + + def get_gt_filenames( gt_file_data: bytes, data_filenames: List[str], *, manifest: TaskManifest ) -> List[str]: @@ -1093,6 +2036,11 @@ def create_task(escrow_address: str, chain_id: int) -> None: task_builder.set_logger(logger) task_builder.build() + elif manifest.annotation.type in [TaskType.image_skeletons_from_boxes]: + with SkeletonsFromBoxesTaskBuilder(manifest, escrow_address, chain_id) as task_builder: + task_builder.set_logger(logger) + task_builder.build() + else: raise Exception(f"Unsupported task type {manifest.annotation.type}") diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index 8176203fee..c505892c23 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -2,10 +2,11 @@ from src.core.config import Config from src.core.manifest import TaskManifest +from src.core.manifest import parse_manifest as _parse_manifest def parse_manifest(manifest: dict) -> TaskManifest: - return TaskManifest.parse_obj(manifest) + return _parse_manifest(manifest) def compose_assignment_url(task_id, job_id) -> str: From adf435991a1989d5f25d4dae0f391de1634018a8 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 2 Feb 2024 18:33:50 +0200 Subject: [PATCH 27/82] Refactor and fix some errors --- .../src/core/tasks/skeletons_from_boxes.py | 40 +-- .../src/handlers/job_creation.py | 50 +-- .../annotations/person_keypoints_default.json | 1 + .../annotations/instances_default.json | 329 ++++++++++++++++++ .../annotations/person_keypoints_default.json | 1 + .../manifest_skeletons_from_boxes_local.json | 51 +++ 6 files changed, 431 insertions(+), 41 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json create mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py index 853877361a..fbbcc260c5 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -50,23 +50,23 @@ class TilesetInfo: # TilesetMap = Dict[int, TilesetInfo] -class TaskMetaLayout: - TILESET_MAP_FILENAME = "tileset_map.json" - TILESET_NAME_PATTERN = "tileset-{}" - - @classmethod - def make_tileset_sample_id(cls, tileset_id: int) -> str: - return cls.TILESET_NAME_PATTERN.format(tileset_id) - - -class TaskMetaSerializer: - def dump_tileset_map(tile_map: TilesetMap, filename: str): - tile_map = {str(k): attrs.asdict(v) for k, v in tile_map.items()} - return dump_json(filename, tile_map, indent=True, append_newline=True) - - def parse_tileset_map(filename: str) -> TilesetMap: - data = parse_json(filename) - return { - int(k): TilesetInfo(tiles=[TileInfo(**vv) for vv in v.pop("tiles", [])], **v) - for k, v in data.items() - } +# class TaskMetaLayout: +# TILESET_MAP_FILENAME = "tileset_map.json" +# TILESET_NAME_PATTERN = "tileset-{}" + +# @classmethod +# def make_tileset_sample_id(cls, tileset_id: int) -> str: +# return cls.TILESET_NAME_PATTERN.format(tileset_id) + + +# class TaskMetaSerializer: +# def dump_tileset_map(tile_map: TilesetMap, filename: str): +# tile_map = {str(k): attrs.asdict(v) for k, v in tile_map.items()} +# return dump_json(filename, tile_map, indent=True, append_newline=True) + +# def parse_tileset_map(filename: str) -> TilesetMap: +# data = parse_json(filename) +# return { +# int(k): TilesetInfo(tiles=[TileInfo(**vv) for vv in v.pop("tiles", [])], **v) +# for k, v in data.items() +# } diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index cd5da600a1..ce9e5a61d4 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -400,7 +400,7 @@ def _validate_points_annotations(self): self.input_points_dataset.remove(*excluded_sample) if excluded_samples: - self.logger.info( + self.logger.warning( "Some samples were excluded due to errors found: {}".format( self._format_list([m for _, m in excluded_samples], separator="\n") ) @@ -545,7 +545,7 @@ def _prepare_gt(self): ) ) elif excluded_boxes_messages: - self.logger.info(self._format_list(excluded_boxes_messages, separator="\n")) + self.logger.warning(self._format_list(excluded_boxes_messages, separator="\n")) gt_labels_without_anns = [ gt_label_cat[label_id] @@ -599,7 +599,7 @@ def _estimate_roi_sizes(self): if classes_with_default_roi: label_cat = self.gt_dataset.categories()[dm.AnnotationType.label] - self.logger.debug( + self.logger.warning( "Some classes will use the full image instead of RoI" "- too few GT provided: {}".format( self._format_list( @@ -1003,7 +1003,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): # credentials=BucketCredentials() "Exchange Oracle's private bucket info" - self.min_label_gt_samples = 5 + self.min_label_gt_samples = 2 # TODO: find good threshold self.max_discarded_threshold = 0.5 """ @@ -1084,14 +1084,16 @@ def _validate_gt_labels(self): for skeleton_label in self.manifest.annotation.labels: manifest_labels.add((skeleton_label.name, "")) for node_label in skeleton_label.nodes: - manifest_labels.add((skeleton_label.name, node_label)) + manifest_labels.add((node_label, skeleton_label.name)) if gt_labels - manifest_labels: raise DatasetValidationError( "GT labels do not match job labels. Unknown labels: {}".format( self._format_list( - label_name if not parent_name else f"{parent_name}.{label_name}" - for label_name, parent_name in gt_labels - manifest_labels + [ + label_name if not parent_name else f"{parent_name}.{label_name}" + for label_name, parent_name in gt_labels - manifest_labels + ] ), ) ) @@ -1191,8 +1193,8 @@ def _validate_boxes_annotations(self): continue if not ( - (0 <= bbox.x < bbox.x + bbox.w < image_w) - and (0 <= bbox.y < bbox.y + bbox.h < image_h) + (0 <= bbox.x < bbox.x + bbox.w <= image_w) + and (0 <= bbox.y < bbox.y + bbox.h <= image_h) ): message = "Sample '{}': bbox #{} ({}) skipped - " "invalid coordinates".format( sample.id, bbox.id, label_cat[bbox.label].name @@ -1210,7 +1212,7 @@ def _validate_boxes_annotations(self): self.input_boxes_dataset.remove(*excluded_sample) if excluded_samples: - self.logger.info( + self.logger.warning( "Some samples were excluded due to errors found: {}".format( self._format_list([m for _, m in excluded_samples], separator="\n") ) @@ -1267,7 +1269,9 @@ def _prepare_gt(self): skeleton_bbox_mapping = {} # skeleton id -> bbox id for gt_sample in self.input_gt_dataset: boxes_sample = self.input_boxes_dataset.get(gt_sample.id, gt_sample.subset) - assert boxes_sample + # Samples could be discarded, so we just skip them without an error + if not boxes_sample: + continue gt_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] input_boxes = [a for a in boxes_sample.annotations if isinstance(a, dm.Bbox)] @@ -1357,7 +1361,7 @@ def _prepare_gt(self): ) ) elif excluded_skeletons_messages: - self.logger.info(self._format_list(excluded_skeletons_messages, separator="\n")) + self.logger.warning(self._format_list(excluded_skeletons_messages, separator="\n")) labels_with_few_gt = [ gt_label_cat[label_id] @@ -1366,7 +1370,7 @@ def _prepare_gt(self): ] if labels_with_few_gt: raise DatasetValidationError( - "No matching GT boxes/points annotations found for some classes: {}".format( + "Too few matching GT boxes/points annotations found for some classes: {}".format( self._format_list(labels_with_few_gt) ) ) @@ -1518,10 +1522,9 @@ def _prepare_tileset_params( roi_ids=[roi.bbox_id for roi in rois], all_roi_coords=all_roi_coords, all_tile_coords=all_tile_coords, - grid_size=grid_size, frame_size=frame_size[::-1], roi_border_size=roi_border, - tile_margin=tile_margin, + tile_margin_size=tile_margin, ) def _mangle_filenames(self): @@ -1588,6 +1591,7 @@ def _prepare_job_params(self): job_gt_count = max( self.manifest.validation.val_size, int(gt_percent * len(job_data_roi_ids)) ) + job_gt_count = min(len(label_gt_roi_ids), job_gt_count) # TODO: maybe use size bins and take from them to match data sizes job_gt_roi_ids = random.sample(label_gt_roi_ids, k=job_gt_count) @@ -1599,8 +1603,12 @@ def _prepare_job_params(self): self.job_params = job_params + self._prepare_dataset_tileset_params() + def _upload_task_meta(self): - raise NotImplementedError + # TODO: + # raise NotImplementedError + pass def _create_tileset_frame( self, @@ -1615,7 +1623,7 @@ def _create_tileset_frame( for grid_pos, roi_id in enumerate(tileset_params.roi_ids): roi_coords = tileset_params.all_roi_coords[grid_pos].astype(int) - roi_image = roi_images[roi_id] + roi_image = roi_images[grid_pos] if self.embed_tile_border: tile_coords = tileset_params.all_tile_coords[grid_pos].astype(int) @@ -1659,7 +1667,7 @@ def _draw_roi_bbox(self, roi_image: np.ndarray, bbox: dm.Bbox) -> np.ndarray: def _create_and_upload_tilesets(self): assert self.tileset_filenames is not _unset - assert self.tileset_infos is not _unset + assert self.tileset_params is not _unset # TODO: optimize downloading, this implementation won't work for big datasets src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) @@ -1670,10 +1678,10 @@ def _create_and_upload_tilesets(self): dst_client = self._make_cloud_storage_client(dst_bucket) image_id_to_filename = { - sample.attributes["id"]: sample.image.path for sample in self.input_points_dataset + sample.attributes["id"]: sample.image.path for sample in self.input_boxes_dataset } - filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} + filename_to_sample = {sample.image.path: sample for sample in self.input_boxes_dataset} _roi_info_key = lambda e: e.original_image_key roi_info_by_image: Dict[str, Sequence[skeletons_from_boxes_task.RoiInfo]] = { @@ -1857,8 +1865,8 @@ def build(self): # Task configuration creation self._prepare_gt() self._prepare_roi_infos() - self._mangle_filenames() self._prepare_job_params() + self._mangle_filenames() # Data preparation self._create_and_upload_tilesets() diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json new file mode 100644 index 0000000000..7a3a7fc2cc --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json @@ -0,0 +1 @@ +{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":"","keypoints":["head","r front leg","l front leg","r back leg","l back leg","neck","torso"],"skeleton":[[7,4],[6,2],[6,1],[5,7],[7,6],[6,3]]},{"id":9,"name":"dog","supercategory":"","keypoints":["nose","l ear","r ear"],"skeleton":[[3,1],[2,1]]}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":26598.009200000004,"bbox":[276.84,456.97,112.28,236.89],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[343.77,456.97,2,389.12,683.69,2,326.35,687.92,2,290.69,693.86,2,359.15,685.99,2,342.5,527.96,2,276.84,634.98,2],"num_keypoints":7},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":17348.673300000002,"bbox":[874.63,310.35,139.47,124.39],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[939.77,310.35,2,898.52,434.74,2,994.96,430.32,2,874.63,413.0,2,1014.1,432.35,2,987.86,353.88,2,1007.35,399.89,2],"num_keypoints":7},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":13275.416700000005,"bbox":[377.52,253.64,193.83,68.49],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[377.52,253.64,2,424.44,306.56,2,410.48,303.45,2,519.36,319.3,2,536.8,322.13,2,437.73,263.27,2,571.35,292.16,2],"num_keypoints":7},{"id":4,"image_id":6,"category_id":9,"segmentation":[],"area":26851.028500000004,"bbox":[290.23,467.54,195.95,137.03],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[486.18,604.57,2,459.0,467.54,2,290.23,508.66,2],"num_keypoints":3},{"id":5,"image_id":6,"category_id":9,"segmentation":[],"area":17102.630999999998,"bbox":[681.32,181.06,277.55,61.62],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[958.87,242.68,2,854.96,181.06,2,681.32,241.06,2],"num_keypoints":3}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json new file mode 100644 index 0000000000..0cacc7f0b9 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json @@ -0,0 +1,329 @@ +{ + "licenses": [{ "name": "", "id": 0, "url": "" }], + "info": { + "contributor": "", + "date_created": "", + "description": "", + "url": "", + "version": "", + "year": "" + }, + "categories": [ + { "id": 1, "name": "cat", "supercategory": "" }, + { "id": 2, "name": "dog", "supercategory": "" } + ], + "images": [ + { + "id": 1, + "width": 1279, + "height": 854, + "file_name": "img1.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 2, + "width": 800, + "height": 1039, + "file_name": "img10.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 3, + "width": 900, + "height": 600, + "file_name": "img11.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 4, + "width": 1280, + "height": 879, + "file_name": "img12.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 5, + "width": 1280, + "height": 1920, + "file_name": "img13.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 6, + "width": 1280, + "height": 1295, + "file_name": "img2.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 7, + "width": 1279, + "height": 853, + "file_name": "img3.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 8, + "width": 1280, + "height": 2278, + "file_name": "img4.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 9, + "width": 1280, + "height": 1039, + "file_name": "img5.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 10, + "width": 1280, + "height": 1920, + "file_name": "img6.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 11, + "width": 1280, + "height": 879, + "file_name": "img7.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 12, + "width": 1280, + "height": 853, + "file_name": "img8.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + }, + { + "id": 13, + "width": 800, + "height": 853, + "file_name": "img9.jpg", + "license": 0, + "flickr_url": "", + "coco_url": "", + "date_captured": 0 + } + ], + "annotations": [ + { + "id": 1, + "image_id": 1, + "category_id": 1, + "segmentation": [], + "area": 37089.83899999999, + "bbox": [336.68, 207.62, 272.9, 135.91], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 2, + "image_id": 1, + "category_id": 1, + "segmentation": [], + "area": 45261.450599999975, + "bbox": [855.72, 252.56, 222.59, 203.34], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 3, + "image_id": 1, + "category_id": 1, + "segmentation": [], + "area": 50793.008, + "bbox": [243.58, 398.11, 164.8, 308.21], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 4, + "image_id": 2, + "category_id": 2, + "segmentation": [], + "area": 347397.2964000001, + "bbox": [176.67, 169.9, 429.66, 808.54], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 5, + "image_id": 3, + "category_id": 1, + "segmentation": [], + "area": 356013.414, + "bbox": [178.2, 104.51, 721.8, 493.23], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 6, + "image_id": 4, + "category_id": 2, + "segmentation": [], + "area": 222540.6008, + "bbox": [122.84, 283.66, 445.01, 500.08], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 7, + "image_id": 4, + "category_id": 2, + "segmentation": [], + "area": 321147.2111999999, + "bbox": [347.54, 155.88, 592.61, 541.92], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 8, + "image_id": 5, + "category_id": 2, + "segmentation": [], + "area": 969726.6356, + "bbox": [37.2, 666.4, 1063.46, 911.86], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 9, + "image_id": 6, + "category_id": 2, + "segmentation": [], + "area": 446568.5952, + "bbox": [105.23, 398.47, 524.16, 851.97], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 10, + "image_id": 6, + "category_id": 2, + "segmentation": [], + "area": 439902.1328, + "bbox": [585.6, 138.82, 417.04, 1054.82], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 11, + "image_id": 7, + "category_id": 1, + "segmentation": [], + "area": 266808.472, + "bbox": [320.94, 3.79, 319.6, 834.82], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 12, + "image_id": 8, + "category_id": 1, + "segmentation": [], + "area": 1711147.0188, + "bbox": [156.14, 570.92, 1101.89, 1552.92], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 13, + "image_id": 9, + "category_id": 2, + "segmentation": [], + "area": 350061.43840000004, + "bbox": [431.08, 169.9, 432.26, 809.84], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 14, + "image_id": 10, + "category_id": 2, + "segmentation": [], + "area": 975123.9674000001, + "bbox": [170.74, 665.17, 1077.89, 904.66], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 15, + "image_id": 11, + "category_id": 2, + "segmentation": [], + "area": 323659.9134, + "bbox": [340.39, 153.13, 594.81, 544.14], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 16, + "image_id": 11, + "category_id": 2, + "segmentation": [], + "area": 230117.52299999996, + "bbox": [713.8, 285.31, 448.31, 513.3], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 17, + "image_id": 12, + "category_id": 1, + "segmentation": [], + "area": 373458.2508, + "bbox": [155.79, 229.86, 757.86, 492.78], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + }, + { + "id": 18, + "image_id": 13, + "category_id": 1, + "segmentation": [], + "area": 267909.6806, + "bbox": [397.9, 2.18, 321.74, 832.69], + "iscrowd": 0, + "attributes": { "occluded": false, "rotation": 0.0 } + } + ] +} diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json new file mode 100644 index 0000000000..7a3a7fc2cc --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json @@ -0,0 +1 @@ +{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":"","keypoints":["head","r front leg","l front leg","r back leg","l back leg","neck","torso"],"skeleton":[[7,4],[6,2],[6,1],[5,7],[7,6],[6,3]]},{"id":9,"name":"dog","supercategory":"","keypoints":["nose","l ear","r ear"],"skeleton":[[3,1],[2,1]]}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":26598.009200000004,"bbox":[276.84,456.97,112.28,236.89],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[343.77,456.97,2,389.12,683.69,2,326.35,687.92,2,290.69,693.86,2,359.15,685.99,2,342.5,527.96,2,276.84,634.98,2],"num_keypoints":7},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":17348.673300000002,"bbox":[874.63,310.35,139.47,124.39],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[939.77,310.35,2,898.52,434.74,2,994.96,430.32,2,874.63,413.0,2,1014.1,432.35,2,987.86,353.88,2,1007.35,399.89,2],"num_keypoints":7},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":13275.416700000005,"bbox":[377.52,253.64,193.83,68.49],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[377.52,253.64,2,424.44,306.56,2,410.48,303.45,2,519.36,319.3,2,536.8,322.13,2,437.73,263.27,2,571.35,292.16,2],"num_keypoints":7},{"id":4,"image_id":6,"category_id":9,"segmentation":[],"area":26851.028500000004,"bbox":[290.23,467.54,195.95,137.03],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[486.18,604.57,2,459.0,467.54,2,290.23,508.66,2],"num_keypoints":3},{"id":5,"image_id":6,"category_id":9,"segmentation":[],"area":17102.630999999998,"bbox":[681.32,181.06,277.55,61.62],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[958.87,242.68,2,854.96,181.06,2,681.32,241.06,2],"num_keypoints":3}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json b/packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json new file mode 100644 index 0000000000..617280c03e --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json @@ -0,0 +1,51 @@ +{ + "data": { + "data_url": "http://127.0.0.1:9010/datasets/", + "boxes_url": "http://127.0.0.1:9010/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco%201.0-good/annotations/instances_default.json" + }, + "annotation": { + "labels": [ + { + "name": "cat", + "type": "skeleton", + "nodes": [ + "head", + "neck", + "torso", + "l front leg", + "r front leg", + "l back leg", + "r back leg" + ], + "joints": [ + [0, 1], + [1, 2], + [1, 3], + [1, 4], + [3, 5], + [3, 6] + ] + }, + { + "name": "dog", + "type": "skeleton", + "nodes": ["nose", "l ear", "r ear"], + "joints": [ + [0, 1], + [0, 2] + ] + } + ], + "description": "Brief task description", + "user_guide": "Task description (markdown)", + "type": "IMAGE_SKELETONS_FROM_BOXES", + "job_size": 10, + "max_time": 1800 + }, + "validation": { + "min_quality": 0.8, + "val_size": 2, + "gt_url": "http://127.0.0.1:9010/datasets/gt_skeletons/annotations/person_keypoints_default.json" + }, + "job_bounty": "0.05" +} From 6900a11a9dd5967ff2bbc4424d35fad22875ca02 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 5 Feb 2024 11:27:07 +0200 Subject: [PATCH 28/82] Implement job uploading --- .../src/core/tasks/skeletons_from_boxes.py | 4 + .../exchange-oracle/src/cvat/api_calls.py | 13 +- .../src/handlers/job_creation.py | 469 ++++++------------ 3 files changed, 160 insertions(+), 326 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py index fbbcc260c5..8793304ad0 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -15,6 +15,10 @@ class RoiInfo: bbox_x: int bbox_y: int bbox_label: int + + # RoI is centered on the bbox center + # Coordinates can be out of image boundaries. + # In this case RoI includes extra margins to be centered on bbox center roi_x: int roi_y: int roi_w: int diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 566f9d039a..058ab2d643 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -126,15 +126,20 @@ def create_cloudstorage( def create_project( - escrow_address: str, labels: list, *, user_guide: str = "" + escrow_address: str, *, labels: Optional[list] = None, user_guide: str = "" ) -> models.ProjectRead: logger = logging.getLogger("app") with get_api_client() as api_client: + kwargs = {} + + if labels is not None: + kwargs["labels"] = labels + try: (project, response) = api_client.projects_api.create( models.ProjectWriteRequest( name=escrow_address, - labels=labels, + **kwargs ) ) if user_guide: @@ -278,7 +283,6 @@ def put_task_data( cloudstorage_id: int, *, filenames: Optional[list[str]] = None, - job_filenames: Optional[list[list[str]]] = None, sort_images: bool = True, ) -> None: logger = logging.getLogger("app") @@ -290,9 +294,6 @@ def put_task_data( else: kwargs["filename_pattern"] = "*" - if job_filenames: - kwargs["job_file_mapping"] = job_filenames - data_request = models.DataRequest( chunk_size=Config.cvat_config.cvat_job_segment_size, cloud_storage_id=cloudstorage_id, diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index ce9e5a61d4..09b8cf4017 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -933,26 +933,6 @@ class SkeletonsFromBoxesTaskBuilder: class _JobParams: label_id: int roi_ids: List[int] - tileset_ids: _MaybeUnset[List[int]] = _unset - - @dataclass - class _TilesetParams: - roi_ids: List[int] - - all_tile_coords: np.ndarray - "N x (x, y, w, h)" - - all_roi_coords: np.ndarray - "N x (x, y, w, h)" - - frame_size: np.ndarray - "h, w" - - roi_border_size: np.ndarray - "h, w" - - tile_margin_size: np.ndarray - "h, w" def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.exit_stack = ExitStack() @@ -970,14 +950,17 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset self.input_boxes_dataset: _MaybeUnset[dm.Dataset] = _unset - self.tileset_filenames: _MaybeUnset[Dict[int, str]] = _unset + self.roi_filenames: _MaybeUnset[Dict[int, str]] = _unset self.job_params: _MaybeUnset[List[self._JobParams]] = _unset self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset # Configuration / constants + self.job_size_mult = 6 + "Job size multiplier" + # TODO: consider WebP if produced files are too big - self.tileset_file_ext = ".png" # supposed to be lossless and reasonably compressing - "File extension for tileset images, with leading dot (.) included" + self.roi_file_ext = ".png" # supposed to be lossless and reasonably compressing + "File extension for RoI images, with leading dot (.) included" self.list_display_threshold = 5 "The maximum number of rendered list items in a message" @@ -993,10 +976,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.embed_tile_border = True self.roi_embedded_bbox_color = (0, 255, 255) # BGR - - self.tileset_size = (3, 2) # W, H - self.tileset_background_color = (245, 240, 242) # BGR - CVAT background color - self.tile_border_color = (255, 255, 255) + self.roi_background_color = (245, 240, 242) # BGR - CVAT background color self.oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) # TODO: add @@ -1388,24 +1368,18 @@ def _prepare_roi_infos(self): if not isinstance(bbox, dm.Bbox): continue - original_bbox_x = int(bbox.x) - original_bbox_y = int(bbox.y) - - image_h, image_w = sample.image.size - - roi_margin_w = bbox.w * self.roi_size_mult - roi_margin_h = bbox.h * self.roi_size_mult + # RoI is centered on bbox center + original_bbox_cx = int(bbox.x + bbox.w / 2) + original_bbox_cy = int(bbox.y + bbox.h / 2) - roi_left = max(0, original_bbox_x - int(roi_margin_w / 2)) - roi_top = max(0, original_bbox_y - int(roi_margin_h / 2)) - roi_right = min(image_w, original_bbox_x + ceil(roi_margin_w / 2)) - roi_bottom = min(image_h, original_bbox_y + ceil(roi_margin_h / 2)) + roi_w = ceil(bbox.w * self.roi_size_mult) + roi_h = ceil(bbox.h * self.roi_size_mult) - roi_w = roi_right - roi_left - roi_h = roi_bottom - roi_top + roi_x = original_bbox_cx - int(roi_w / 2) + roi_y = original_bbox_cy - int(roi_h / 2) - new_bbox_x = original_bbox_x - roi_left - new_bbox_y = original_bbox_y - roi_top + new_bbox_x = original_bbox_cx - roi_x + new_bbox_y = original_bbox_cy - roi_y rois.append( skeletons_from_boxes_task.RoiInfo( @@ -1414,8 +1388,8 @@ def _prepare_roi_infos(self): bbox_label=bbox.label, bbox_x=new_bbox_x, bbox_y=new_bbox_y, - roi_x=roi_left, - roi_y=roi_top, + roi_x=roi_x, + roi_y=roi_y, roi_w=roi_w, roi_h=roi_h, ) @@ -1423,122 +1397,17 @@ def _prepare_roi_infos(self): self.roi_infos = rois - def _prepare_dataset_tileset_params(self): - assert self.gt_dataset is not _unset - assert self.input_boxes_dataset is not _unset - assert self.roi_infos is not _unset - assert self.job_params is not _unset - - combined_tileset_params: Dict[int, self._TilesetParams] = {} - self.tileset_params = combined_tileset_params - roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self.roi_infos} - - for job_params in self.job_params: - assert job_params.tileset_ids is _unset - tileset_ids = [] - for tileset_roi_ids in take_by(job_params.roi_ids, np.prod(self.tileset_size)): - tileset_id = len(combined_tileset_params) - combined_tileset_params[tileset_id] = self._prepare_tileset_params( - [roi_info_by_id[roi_id] for roi_id in tileset_roi_ids], - grid_size=self.tileset_size, - ) - tileset_ids.append(tileset_id) - - job_params.tileset_ids = tileset_ids - - self.tileset_params = combined_tileset_params - - @classmethod - def _prepare_tileset_params( - cls, - rois: skeletons_from_boxes_task.RoiInfos, - *, - grid_size: Tuple[int, int], - ) -> _TilesetParams: - """ - RoI is inside tile, centered - border is for RoI - border is inside tile or margin - margin is for tile - margin size is shared between 2 adjacent tiles - - A regular row/col looks like (b - border, m - margin, T - tile): - b|RoI1|b|tile - RoI1 - b|m - 2 * b|tile - RoI2 - b|b|RoI2|b - - A tileset looks like this: - bbbbbbb - bTmTmTb - bmmmmmb - bTmTmTb - bbbbbbb - """ - - assert len(rois) <= np.prod(grid_size) - - # FIXME: RoI size is not limited by size, it can lead to highly uneven tiles - base_tile_size = np.max([(sample.roi_h, sample.roi_w) for sample in rois], axis=0).astype( - int - ) - - grid_size = np.array(grid_size, dtype=int) - roi_border = np.array((2, 2), dtype=int) - tile_margin = np.array((20, 20), dtype=int) - assert np.all(2 * roi_border < tile_margin) - - frame_size = ( - base_tile_size * grid_size[::-1] + tile_margin * (grid_size[::-1] - 1) + 2 * roi_border - ) - - all_tile_coords = np.zeros((np.prod(grid_size), 4), dtype=float) # x, y, w, h - for grid_id, grid_pos in enumerate(product(range(grid_size[1]), range(grid_size[0]))): - grid_pos = np.array(grid_pos[::-1], dtype=int) - tile_coords = all_tile_coords[grid_id] - tile_coords[:2] = ( - base_tile_size[::-1] * grid_pos - + tile_margin / 2 * grid_pos - + tile_margin / 2 * np.clip(grid_pos - 1, 0, None) - + roi_border * (grid_pos > 0) - ) - tile_coords[2:] = ( - base_tile_size[::-1] - + tile_margin / 2 - + np.where( - (grid_pos == 0) | (grid_pos == (grid_size - 1)), - roi_border, - tile_margin / 2, - ) - ) - - all_roi_coords = np.zeros((np.prod(grid_size), 4), dtype=int) # x, y, w, h - for grid_id, roi_info in enumerate(rois): - tile_coords = all_tile_coords[grid_id] - roi_coords = all_roi_coords[grid_id] - - roi_size = np.array((roi_info.roi_h, roi_info.roi_w), dtype=int) - roi_offset = tile_coords[2:] / 2 - roi_size[::-1] / 2 - roi_coords[:] = [*(tile_coords[:2] + roi_offset), *roi_size[::-1]] # (x, y, w, h) - - return cls._TilesetParams( - roi_ids=[roi.bbox_id for roi in rois], - all_roi_coords=all_roi_coords, - all_tile_coords=all_tile_coords, - frame_size=frame_size[::-1], - roi_border_size=roi_border, - tile_margin_size=tile_margin, - ) - def _mangle_filenames(self): """ Mangle filenames in the dataset to make them less recognizable by annotators and hide private dataset info """ - assert self.tileset_params is not _unset + assert self.roi_infos is not _unset # TODO: maybe add different names for the same GT images in # different jobs to make them even less recognizable - self.tileset_filenames = { - tileset_id: str(uuid.uuid4()) + self.tileset_file_ext - for tileset_id in self.tileset_params.keys() + self.roi_filenames = { + roi_info.bbox_id: str(uuid.uuid4()) + self.roi_file_ext for roi_info in self.roi_infos } def _prepare_job_params(self): @@ -1549,31 +1418,19 @@ def _prepare_job_params(self): # 1 job per task, 1 task for each point label # # Unlike other task types, here we use a grid of RoIs, - # so the absolute job size numbers from manifest are multiplied by the grid size. + # so the absolute job size numbers from manifest are multiplied by the job size multiplier. # Then, we add a percent of job tiles for validation, keeping the requested ratio. - gt_percent = self.manifest.validation.val_size / (self.manifest.annotation.job_size or 1) + gt_ratio = self.manifest.validation.val_size / (self.manifest.annotation.job_size or 1) + job_size_mult = self.job_size_mult job_params: List[self._JobParams] = [] - _roi_key_label = lambda roi_info: roi_info.bbox_label - rois_by_label = { - label: list(g) - for label, g in groupby(sorted(self.roi_infos, key=_roi_key_label), key=_roi_key_label) - } - roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self.roi_infos} - - tileset_size = np.prod(self.tileset_size) - for label, label_rois in rois_by_label.items(): - # FIXME: RoI sizes are not limited, so they can occupy up to the whole image. - # Sort by frame size to make tile sizes more aligned. - # This doesn't totally solve the problem, but makes things better. - label_rois = sorted(label_rois, key=lambda roi_info: roi_info.roi_w * roi_info.roi_h) - + for label_id, _ in enumerate(self.manifest.annotation.labels): label_gt_roi_ids = set( roi_id for roi_id in self.skeleton_bbox_mapping.values() - if roi_info_by_id[roi_id].bbox_label == label + if roi_info_by_id[roi_id].bbox_label == label_id ) label_data_roi_ids = [ @@ -1581,93 +1438,77 @@ def _prepare_job_params(self): for roi_info in self.roi_infos if roi_info.bbox_id not in label_gt_roi_ids ] - # Can't really shuffle if we sort by size - # TODO: maybe shuffle within bins by size - # random.shuffle(label_data_roi_ids) + random.shuffle(label_data_roi_ids) for job_data_roi_ids in take_by( - label_data_roi_ids, tileset_size * self.manifest.annotation.job_size + label_data_roi_ids, int(job_size_mult * self.manifest.annotation.job_size) ): job_gt_count = max( - self.manifest.validation.val_size, int(gt_percent * len(job_data_roi_ids)) + self.manifest.validation.val_size, int(gt_ratio * len(job_data_roi_ids)) ) job_gt_count = min(len(label_gt_roi_ids), job_gt_count) - # TODO: maybe use size bins and take from them to match data sizes job_gt_roi_ids = random.sample(label_gt_roi_ids, k=job_gt_count) job_roi_ids = list(job_data_roi_ids) + list(job_gt_roi_ids) random.shuffle(job_roi_ids) - job_params.append(self._JobParams(label_id=label, roi_ids=job_roi_ids)) + job_params.append(self._JobParams(label_id=label_id, roi_ids=job_roi_ids)) self.job_params = job_params - self._prepare_dataset_tileset_params() - def _upload_task_meta(self): # TODO: # raise NotImplementedError pass - def _create_tileset_frame( - self, - tileset_params: _TilesetParams, - roi_images: dict[int, np.ndarray], + def _extract_roi( + self, source_pixels: np.ndarray, roi_info: skeletons_from_boxes_task.RoiInfo ) -> np.ndarray: - frame = np.zeros((*tileset_params.frame_size, 3), dtype=np.uint8) - frame[:, :] = self.tileset_background_color - - roi_border = tileset_params.roi_border_size - border_color = self.tile_border_color - - for grid_pos, roi_id in enumerate(tileset_params.roi_ids): - roi_coords = tileset_params.all_roi_coords[grid_pos].astype(int) - roi_image = roi_images[grid_pos] - - if self.embed_tile_border: - tile_coords = tileset_params.all_tile_coords[grid_pos].astype(int) - frame[ - tile_coords[1] : tile_coords[1] + tile_coords[3], - tile_coords[0] : tile_coords[0] + tile_coords[2], - ] = 0 - - frame[ - tile_coords[1] + 1 : tile_coords[1] + tile_coords[3] - 2, - tile_coords[0] + 1 : tile_coords[0] + tile_coords[2] - 2, - ] = self.tileset_background_color - - border_coords = np.array( - [*(roi_coords[:2] - roi_border), *(roi_coords[2:] + 2 * roi_border)], - dtype=int, - ) - frame[ - border_coords[1] : border_coords[1] + border_coords[3], - border_coords[0] : border_coords[0] + border_coords[2], - ] = border_color + img_h, img_w, *_ = source_pixels.shape - frame[ - roi_coords[1] : roi_coords[1] + roi_coords[3], - roi_coords[0] : roi_coords[0] + roi_coords[2], - ] = roi_image[ - : roi_coords[3], : roi_coords[2] - ] # in some cases size can be truncated on previous iterations + roi_pixels = source_pixels[ + max(0, roi_info.roi_y) : min(img_h, roi_info.roi_y + roi_info.roi_h), + max(0, roi_info.roi_x) : min(img_w, roi_info.roi_x + roi_info.roi_w), + ] + + if not ( + (0 <= roi_info.roi_x < roi_info.roi_x + roi_info.roi_w < img_w) + and (0 <= roi_info.roi_y < roi_info.roi_y + roi_info.roi_h < img_h) + ): + # Coords can be outside the original image + # In this case a border should be added to RoI, so that the image was centered on bbox + wrapped_roi_pixels = np.zeros((roi_info.roi_h, roi_info.roi_w, 3), dtype=np.float32) + wrapped_roi_pixels[:, :] = self.roi_background_color + + dst_y = max(-roi_info.roi_y, 0) + dst_x = max(-roi_info.roi_x, 0) + wrapped_roi_pixels[ + dst_y : dst_y + roi_pixels.shape[0], + dst_x : dst_x + roi_pixels.shape[1], + ] = roi_pixels + + roi_pixels = wrapped_roi_pixels + else: + roi_pixels = roi_pixels.copy() - return frame + return roi_pixels def _draw_roi_bbox(self, roi_image: np.ndarray, bbox: dm.Bbox) -> np.ndarray: + roi_cy = roi_image.shape[0] // 2 + roi_cx = roi_image.shape[1] // 2 return cv2.rectangle( roi_image, - tuple(map(int, (bbox.x, bbox.y))), - tuple(map(int, (bbox.x + bbox.w, bbox.y + bbox.h))), + tuple(map(int, (roi_cx - bbox.w / 2, roi_cy - bbox.h / 2))), + tuple(map(int, (roi_cx + bbox.w / 2, roi_cy + bbox.h / 2))), self.roi_embedded_bbox_color, 2, # TODO: maybe improve line thickness cv2.LINE_4, ) - def _create_and_upload_tilesets(self): - assert self.tileset_filenames is not _unset - assert self.tileset_params is not _unset + def _extract_and_upload_rois(self): + assert self.roi_filenames is not _unset + assert self.roi_infos is not _unset # TODO: optimize downloading, this implementation won't work for big datasets src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) @@ -1696,7 +1537,6 @@ def _create_and_upload_tilesets(self): if isinstance(bbox, dm.Bbox) } - roi_images = {} for filename in self.input_filenames: image_roi_infos = roi_info_by_image.get(filename, []) if not image_roi_infos: @@ -1717,32 +1557,46 @@ def _create_and_upload_tilesets(self): ) for roi_info in image_roi_infos: - roi_pixels = image_pixels[ - roi_info.roi_y : roi_info.roi_y + roi_info.roi_h, - roi_info.roi_x : roi_info.roi_x + roi_info.roi_w, - ] + roi_pixels = self._extract_roi(image_pixels, roi_info) if self.embed_bbox_in_roi_image: roi_pixels = self._draw_roi_bbox(roi_pixels, bbox_by_id[roi_info.bbox_id]) - roi_images[roi_info.bbox_id] = roi_pixels + filename = self.roi_filenames[roi_info.bbox_id] + roi_bytes = encode_image(roi_pixels, os.path.splitext(filename)[-1]) - for tileset_id, tileset_params in self.tileset_params.items(): - tileset_pixels = self._create_tileset_frame( - tileset_params, roi_images=[roi_images[roi_id] for roi_id in tileset_params.roi_ids] - ) + dst_client.create_file( + dst_bucket.url.bucket_name, + filename=compose_data_bucket_filename( + self.escrow_address, self.chain_id, filename + ), + data=roi_bytes, + ) - filename = self.tileset_filenames[tileset_id] - tileset_bytes = encode_image(tileset_pixels, os.path.splitext(filename)[-1]) + def _create_on_cvat(self): + assert self.job_params is not _unset - dst_client.create_file( - dst_bucket.url.bucket_name, - filename=compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), - data=tileset_bytes, + # TODO: Find a way to handle different labels in tasks in a project + _job_params_label_key = lambda ts: ts.label_id + jobs_by_label = { + label_id: list(g) + for label_id, g in groupby( + sorted(self.job_params, key=_job_params_label_key), key=_job_params_label_key ) + } - def _create_on_cvat(self): - assert self.job_params is not _unset + label_specs_by_skeleton = { + skeleton_label_id: [ + { + "name": point_name + if len(self.manifest.annotation.labels) == 1 + else f"{skeleton_label}.{point_name}", + "type": "points", + } + for point_name in skeleton_label.nodes + ] + for skeleton_label_id, skeleton_label in enumerate(self.manifest.annotation.labels) + } input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) oracle_bucket = self.oracle_data_bucket @@ -1760,96 +1614,71 @@ def _create_on_cvat(self): # credentials=... ) - # Create a project - project = cvat_api.create_project( - self.escrow_address, - user_guide=self.manifest.annotation.user_guide, - # TODO: improve guide handling - split for different points - ) - - # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) - - input_data_bucket = parse_bucket_url(self.manifest.data.data_url) - with SessionLocal.begin() as session: - db_service.create_project( - session, - project.id, - cloud_storage.id, - self.manifest.annotation.type, + for label_id, label_jobs in jobs_by_label.items(): + # Create a project. CVAT doesn't support tasks with different labels in a project + project = cvat_api.create_project( self.escrow_address, - self.chain_id, - compose_bucket_url( - input_data_bucket.bucket_name, - bucket_host=input_data_bucket.host_url, - provider=input_data_bucket.provider, - ), - cvat_webhook_id=webhook.id, - ) - db_service.add_project_images( - session, - project.id, - [ - compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) - for fn in self.tileset_filenames.values() - ], + user_guide=self.manifest.annotation.user_guide, + # TODO: improve guide handling - split for different points ) - _job_params_label_key = lambda ts: ts.label_id - jobs_by_label = { - label_id: list(g) - for label_id, g in groupby( - sorted(self.job_params, key=_job_params_label_key), key=_job_params_label_key - ) - } + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.url.bucket_name, + bucket_host=input_data_bucket.url.host_url, + provider=input_data_bucket.url.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images( + session, + project.id, + [ + compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) + for fn in self.roi_filenames.values() + ], + ) - job_label_specs = { - skeleton_label_id: [ - { - "name": point_name - if len(self.manifest.annotation.labels) == 1 - else f"{skeleton_label}.{point_name}", - "type": "points", - } - for point_name in skeleton_label.nodes - ] - for skeleton_label_id, skeleton_label in enumerate(self.manifest.annotation.labels) - } - for skeleton_label_id, skeleton_label_jobs in jobs_by_label.items(): job_filenames_map = [] - for job_filenames_map in skeleton_label_jobs: + for label_job in label_jobs: job_filenames_map.append( [ compose_data_bucket_filename( - self.escrow_address, self.chain_id, self.tileset_filenames[tileset_id] + self.escrow_address, self.chain_id, self.roi_filenames[roi_id] ) - for tileset_id in skeleton_label_jobs.tileset_ids + for roi_id in label_job.roi_ids ] ) - skeleton_label_specs = job_label_specs[skeleton_label_id] - for label_spec in skeleton_label_specs: - task = cvat_api.create_task(project.id, self.escrow_address, labels=[label_spec]) - - with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) - - # Actual task creation in CVAT takes some time, so it's done in an async process. - # The task will be created in DB once 'update:task' or 'update:job' webhook is received. - cvat_api.put_task_data( - task.id, - cloud_storage.id, - filenames=[ - compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) - for job_filenames in job_filenames_map - for fn in job_filenames - ], - sort_images=False, - job_filenames=job_filenames_map, - ) + point_label_specs = label_specs_by_skeleton[label_id] + for label_spec in point_label_specs: + for job_filenames in job_filenames_map: + task = cvat_api.create_task(project.id, self.escrow_address, labels=[label_spec]) + + with SessionLocal.begin() as session: + db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + + # Actual task creation in CVAT takes some time, so it's done in an async process. + # The task will be created in DB once 'update:task' or 'update:job' webhook is received. + cvat_api.put_task_data( + task.id, + cloud_storage.id, + filenames=job_filenames, + sort_images=False, + ) - with SessionLocal.begin() as session: - db_service.create_data_upload(session, cvat_task_id=task.id) + with SessionLocal.begin() as session: + db_service.create_data_upload(session, cvat_task_id=task.id) @classmethod def _make_cloud_storage_client(cls, bucket_info: BucketAccessInfo) -> StorageClient: @@ -1869,7 +1698,7 @@ def build(self): self._mangle_filenames() # Data preparation - self._create_and_upload_tilesets() + self._extract_and_upload_rois() self._upload_task_meta() self._create_on_cvat() From cd3dc98a244d93431a781223ce4eb586cb5d9742 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 6 Feb 2024 14:56:03 +0200 Subject: [PATCH 29/82] Implement downloading --- .../c1e74c227cfe_non_unique_escrows.py | 29 ++ .../cvat/exchange-oracle/src/core/config.py | 2 +- .../src/core/tasks/skeletons_from_boxes.py | 142 ++++--- .../exchange-oracle/src/crons/__init__.py | 6 +- .../src/crons/state_trackers.py | 155 +------- .../exchange-oracle/src/cvat/api_calls.py | 18 +- .../src/handlers/completed_escrows.py | 363 ++++++++++++++++++ .../src/handlers/job_creation.py | 254 +++++++----- .../src/handlers/job_export.py | 255 +++++++++++- .../cvat/exchange-oracle/src/models/cvat.py | 4 +- .../cvat/exchange-oracle/src/services/cvat.py | 35 +- .../exchange-oracle/src/utils/annotations.py | 185 ++++++++- .../test_retrieve_annotations.py | 10 +- 13 files changed, 1118 insertions(+), 340 deletions(-) create mode 100644 packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py create mode 100644 packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py diff --git a/packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py b/packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py new file mode 100644 index 0000000000..f2ea93a456 --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/alembic/versions/c1e74c227cfe_non_unique_escrows.py @@ -0,0 +1,29 @@ +"""non-unique-escrows + +Revision ID: c1e74c227cfe +Revises: 16ecc586d685 +Create Date: 2024-02-05 22:54:42.478270 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils + + +# revision identifiers, used by Alembic. +revision = 'c1e74c227cfe' +down_revision = '16ecc586d685' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('projects_escrow_address_key', 'projects', type_='unique') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint('projects_escrow_address_key', 'projects', ['escrow_address']) + # ### end Alembic commands ### diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index e0b600a2da..7996a802fc 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -75,7 +75,7 @@ class CronConfig: track_assignments_int = int(os.environ.get("TRACK_ASSIGNMENTS_INT", 5)) track_assignments_chunk_size = os.environ.get("TRACK_ASSIGNMENTS_CHUNK_SIZE", 10) - retrieve_annotations_int = int(os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60)) + track_completed_escrows_int = int(os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60)) retrieve_annotations_chunk_size = os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5) diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py index 8793304ad0..b6b680c19a 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -1,6 +1,10 @@ -from typing import Dict, List, Sequence +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence, Tuple import attrs +import datumaro as dm from attrs import frozen from datumaro.util import dump_json, parse_json @@ -8,7 +12,7 @@ # TODO: migrate to pydantic -@frozen +@frozen(kw_only=True) class RoiInfo: original_image_key: int bbox_id: int @@ -30,47 +34,93 @@ def asdict(self) -> dict: RoiInfos = Sequence[RoiInfo] - -@frozen(kw_only=True) -class TileInfo: - roi_id: int - roi_x: float # top left - roi_y: float # top left - roi_w: float - roi_h: float - - -@frozen(kw_only=True) -class TilesetInfo: - id: int - label: int - w: int - h: int - tiles: List[TileInfo] - - -TilesetInfos = Sequence[TilesetInfo] - -# TilesetMap = Dict[int, TilesetInfo] - - -# class TaskMetaLayout: -# TILESET_MAP_FILENAME = "tileset_map.json" -# TILESET_NAME_PATTERN = "tileset-{}" - -# @classmethod -# def make_tileset_sample_id(cls, tileset_id: int) -> str: -# return cls.TILESET_NAME_PATTERN.format(tileset_id) - - -# class TaskMetaSerializer: -# def dump_tileset_map(tile_map: TilesetMap, filename: str): -# tile_map = {str(k): attrs.asdict(v) for k, v in tile_map.items()} -# return dump_json(filename, tile_map, indent=True, append_newline=True) - -# def parse_tileset_map(filename: str) -> TilesetMap: -# data = parse_json(filename) -# return { -# int(k): TilesetInfo(tiles=[TileInfo(**vv) for vv in v.pop("tiles", [])], **v) -# for k, v in data.items() -# } +RoiFilenames = Dict[int, str] + +PointLabelsMapping = Dict[Tuple[str, str], str] +"(skeleton, point) -> job point name" + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + BOXES_FILENAME = "boxes.json" + POINT_LABELS_FILENAME = "point_labels.json" + SKELETON_BBOX_MAPPING_FILENAME = "skeleton_bbox_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_person_keypoints" + BBOX_DATASET_FORMAT = "coco_instances" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return ( + Path(gt_dataset_dir) / "annotations" / "person_keypoints_default.json" + ).read_bytes() + + def serialize_bbox_annotations(self, bbox_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + bbox_dataset_dir = os.path.join(temp_dir, "bbox_dataset") + bbox_dataset.export(bbox_dataset_dir, self.BBOX_DATASET_FORMAT) + return (Path(bbox_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_skeleton_bbox_mapping(self, skeleton_bbox_mapping: SkeletonBboxMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in skeleton_bbox_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def serialize_point_labels(self, point_labels: PointLabelsMapping) -> bytes: + return dump_json( + [ + { + "skeleton_label": k[0], + "point_label": k[1], + "job_point_label": v, + } + for k, v in point_labels.items() + ] + ) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_bbox_annotations(self, bbox_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(bbox_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.BBOX_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_skeleton_bbox_mapping(self, skeleton_bbox_mapping_data: bytes) -> SkeletonBboxMapping: + return {int(k): int(v) for k, v in parse_json(skeleton_bbox_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} + + def parse_point_labels(self, point_labels_data: bytes) -> PointLabelsMapping: + return { + (v["skeleton_label"], v["point_label"]): v["job_point_label"] + for v in parse_json(point_labels_data) + } diff --git a/packages/examples/cvat/exchange-oracle/src/crons/__init__.py b/packages/examples/cvat/exchange-oracle/src/crons/__init__.py index 80865b88fc..aefcb8e248 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/__init__.py @@ -11,8 +11,8 @@ process_outgoing_recording_oracle_webhooks, ) from src.crons.state_trackers import ( - retrieve_annotations, track_assignments, + track_completed_escrows, track_completed_projects, track_completed_tasks, track_task_creation, @@ -54,9 +54,9 @@ def cron_record(): seconds=Config.cron_config.track_completed_tasks_int, ) scheduler.add_job( - retrieve_annotations, + track_completed_escrows, "interval", - seconds=Config.cron_config.retrieve_annotations_int, + seconds=Config.cron_config.track_completed_escrows_int, ) scheduler.add_job( track_task_creation, diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index b79ed8fbdb..5d12e94c10 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -1,29 +1,16 @@ -from typing import Dict, List +from typing import List import src.cvat.api_calls as cvat_api import src.models.cvat as cvat_models -import src.services.cloud.utils as cloud_client import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service -from src.chain.escrow import get_escrow_manifest, validate_escrow -from src.core.annotation_meta import RESULTING_ANNOTATIONS_FILE -from src.core.config import CronConfig, StorageConfig -from src.core.oracle_events import ( - ExchangeOracleEvent_TaskCreationFailed, - ExchangeOracleEvent_TaskFinished, -) -from src.core.storage import compose_results_bucket_filename +from src.core.config import CronConfig +from src.core.oracle_events import ExchangeOracleEvent_TaskCreationFailed from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatus from src.db import SessionLocal from src.db.utils import ForUpdateParams -from src.handlers.job_export import ( - CVAT_EXPORT_FORMAT_MAPPING, - FileDescriptor, - postprocess_annotations, - prepare_annotation_metafile, -) +from src.handlers.completed_escrows import handle_completed_escrows from src.log import ROOT_LOGGER_NAME -from src.utils.assignments import parse_manifest from src.utils.logging import get_function_logger module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" @@ -184,143 +171,13 @@ def track_assignments() -> None: logger.debug("Finishing cron job") -def retrieve_annotations() -> None: - """ - Retrieves and stores completed annotations: - 1. Retrieves annotations from projects with "completed" status - 2. Postprocesses them - 3. Stores annotations in s3 bucket - 4. Prepares a webhook to recording oracle - """ +def track_completed_escrows() -> None: logger = get_function_logger(module_logger) try: logger.debug("Starting cron job") - with SessionLocal.begin() as session: - # Get completed projects from db - projects = cvat_service.get_projects_by_status( - session, - ProjectStatuses.completed, - limit=CronConfig.retrieve_annotations_chunk_size, - for_update=ForUpdateParams(skip_locked=True), - ) - - for project in projects: - # Check if all jobs within the project are completed - if not cvat_service.is_project_completed(session, project.id): - cvat_service.update_project_status( - session, project.id, ProjectStatuses.annotation - ) - continue - - validate_escrow(project.chain_id, project.escrow_address) - - manifest = parse_manifest( - get_escrow_manifest(project.chain_id, project.escrow_address) - ) - - logger.debug( - f"Downloading results for the project (escrow_address={project.escrow_address})" - ) - - jobs = cvat_service.get_jobs_by_cvat_project_id(session, project.cvat_id) - - annotation_format = CVAT_EXPORT_FORMAT_MAPPING[project.job_type] - job_annotations: Dict[int, FileDescriptor] = {} - - # Request dataset preparation beforehand - for job in jobs: - cvat_api.request_job_annotations(job.cvat_id, format_name=annotation_format) - cvat_api.request_project_annotations(project.cvat_id, format_name=annotation_format) - - # Collect raw annotations from CVAT, validate and convert them - # into a recording oracle suitable format - for job in jobs: - job_annotations_file = cvat_api.get_job_annotations( - job.cvat_id, format_name=annotation_format - ) - job_assignment = job.latest_assignment - job_annotations[job.cvat_id] = FileDescriptor( - filename="project_{}-task_{}-job_{}-user_{}-assignment_{}.zip".format( - project.cvat_id, - job.cvat_task_id, - job.cvat_id, - job_assignment.user.cvat_id, - job_assignment.id, - ), - file=job_annotations_file, - ) - - project_annotations_file = cvat_api.get_project_annotations( - project.cvat_id, format_name=annotation_format - ) - project_annotations_file_desc = FileDescriptor( - filename=RESULTING_ANNOTATIONS_FILE, - file=project_annotations_file, - ) - - annotation_files: List[FileDescriptor] = [] - annotation_files.append(project_annotations_file_desc) - - annotation_metafile = prepare_annotation_metafile( - jobs=jobs, job_annotations=job_annotations - ) - annotation_files.extend(job_annotations.values()) - postprocess_annotations( - escrow_address=project.escrow_address, - chain_id=project.chain_id, - annotations=annotation_files, - merged_annotation=project_annotations_file_desc, - manifest=manifest, - project_images=cvat_service.get_project_images(session, project.cvat_id), - ) - - annotation_files.append(annotation_metafile) - storage_client = cloud_client.S3Client( - StorageConfig.provider_endpoint_url(), - access_key=StorageConfig.access_key, - secret_key=StorageConfig.secret_key, - ) - existing_storage_files = set( - f.key - for f in storage_client.list_files( - StorageConfig.data_bucket_name, - prefix=compose_results_bucket_filename( - project.escrow_address, - project.chain_id, - "", - ), - ) - ) - for file_descriptor in annotation_files: - if file_descriptor.filename in existing_storage_files: - continue - - storage_client.create_file( - StorageConfig.data_bucket_name, - compose_results_bucket_filename( - project.escrow_address, - project.chain_id, - file_descriptor.filename, - ), - file_descriptor.file.read(), - ) - - oracle_db_service.outbox.create_webhook( - session, - project.escrow_address, - project.chain_id, - OracleWebhookTypes.recording_oracle, - event=ExchangeOracleEvent_TaskFinished(), - ) - - cvat_service.update_project_status(session, project.id, ProjectStatuses.validation) - - logger.info( - f"The project (escrow_address={project.escrow_address}) " - "is finished, resulting annotations are processed successfully" - ) + handle_completed_escrows(logger) except Exception as error: logger.exception(error) finally: diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 058ab2d643..95120e6e00 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -126,7 +126,7 @@ def create_cloudstorage( def create_project( - escrow_address: str, *, labels: Optional[list] = None, user_guide: str = "" + name: str, *, labels: Optional[list] = None, user_guide: str = "" ) -> models.ProjectRead: logger = logging.getLogger("app") with get_api_client() as api_client: @@ -137,10 +137,7 @@ def create_project( try: (project, response) = api_client.projects_api.create( - models.ProjectWriteRequest( - name=escrow_address, - **kwargs - ) + models.ProjectWriteRequest(name=name, **kwargs) ) if user_guide: api_client.guides_api.create( @@ -239,18 +236,11 @@ def create_cvat_webhook(project_id: int) -> models.WebhookRead: raise -def create_task( - project_id: int, escrow_address: str, *, labels: Optional[dict[str, Any]] = None -) -> models.TaskRead: +def create_task(project_id: int, name: str) -> models.TaskRead: logger = logging.getLogger("app") with get_api_client() as api_client: - kwargs = {} - - if labels: - kwargs["labels"] = labels - task_write_request = models.TaskWriteRequest( - name=escrow_address, + name=name, project_id=project_id, overlap=0, segment_size=Config.cvat_config.cvat_job_segment_size, diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py new file mode 100644 index 0000000000..a5826bea0d --- /dev/null +++ b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py @@ -0,0 +1,363 @@ +import itertools +import logging +from typing import Dict, List, Optional + +from sqlalchemy import exc as db_exc +from sqlalchemy.orm import Session + +import src.cvat.api_calls as cvat_api +import src.models.cvat as cvat_models +import src.services.cloud.utils as cloud_client +import src.services.cvat as cvat_service +import src.services.webhook as oracle_db_service +from src.chain.escrow import get_escrow_manifest, validate_escrow +from src.core.annotation_meta import RESULTING_ANNOTATIONS_FILE +from src.core.config import CronConfig, StorageConfig +from src.core.oracle_events import ExchangeOracleEvent_TaskFinished +from src.core.storage import compose_results_bucket_filename +from src.core.types import OracleWebhookTypes, ProjectStatuses, TaskType +from src.db import SessionLocal +from src.db.utils import ForUpdateParams +from src.handlers.job_export import ( + CVAT_EXPORT_FORMAT_MAPPING, + FileDescriptor, + postprocess_annotations, + prepare_annotation_metafile, +) +from src.utils.assignments import parse_manifest +from src.utils.logging import NullLogger + + +class _CompletedEscrowsHandler: + """ + Retrieves and stores completed annotations: + 1. Retrieves annotations from jobs with "completed" status + 2. Processes them + 3. Stores annotations in s3 bucket + 4. Prepares a webhook to recording oracle + """ + + def __init__(self, logger: Optional[logging.Logger]) -> None: + self.logger = logger or NullLogger() + + def _process_plain_escrows(self): + logger = self.logger + + plain_task_types = [t for t in TaskType if not t == TaskType.image_skeletons_from_boxes] + with SessionLocal.begin() as session: + completed_projects = cvat_service.get_projects_by_status( + session, + ProjectStatuses.completed, + included_types=plain_task_types, + limit=CronConfig.retrieve_annotations_chunk_size, + for_update=ForUpdateParams(skip_locked=True), + ) + + for project in completed_projects: + # Check if all jobs within the project are completed + if not cvat_service.is_project_completed(session, project.id): + cvat_service.update_project_status( + session, project.id, ProjectStatuses.annotation + ) + continue + + try: + # TODO: such escrows can fill all the queried completed projects + # need to improve handling for such projects + # (e.g. cancel depending on the escrow status) + validate_escrow(project.chain_id, project.escrow_address) + except Exception as e: + logger.error( + "Failed to handle completed project id {} for escrow {}: {}".format( + project.cvat_id, project.escrow_address, e + ) + ) + continue + + manifest = parse_manifest( + get_escrow_manifest(project.chain_id, project.escrow_address) + ) + + logger.debug( + f"Downloading results for the project (escrow_address={project.escrow_address})" + ) + + jobs = cvat_service.get_jobs_by_cvat_project_id(session, project.cvat_id) + + annotation_format = CVAT_EXPORT_FORMAT_MAPPING[project.job_type] + job_annotations: Dict[int, FileDescriptor] = {} + + # Request dataset preparation beforehand + for job in jobs: + cvat_api.request_job_annotations(job.cvat_id, format_name=annotation_format) + cvat_api.request_project_annotations(project.cvat_id, format_name=annotation_format) + + # Collect raw annotations from CVAT, validate and convert them + # into a recording oracle suitable format + for job in jobs: + job_annotations_file = cvat_api.get_job_annotations( + job.cvat_id, format_name=annotation_format + ) + job_assignment = job.latest_assignment + job_annotations[job.cvat_id] = FileDescriptor( + filename="project_{}-task_{}-job_{}-user_{}-assignment_{}.zip".format( + project.cvat_id, + job.cvat_task_id, + job.cvat_id, + job_assignment.user.cvat_id, + job_assignment.id, + ), + file=job_annotations_file, + ) + + project_annotations_file = cvat_api.get_project_annotations( + project.cvat_id, format_name=annotation_format + ) + project_annotations_file_desc = FileDescriptor( + filename=RESULTING_ANNOTATIONS_FILE, + file=project_annotations_file, + ) + + annotation_files: List[FileDescriptor] = [] + annotation_files.append(project_annotations_file_desc) + + annotation_metafile = prepare_annotation_metafile( + jobs=jobs, job_annotations=job_annotations + ) + annotation_files.extend(job_annotations.values()) + postprocess_annotations( + escrow_address=project.escrow_address, + chain_id=project.chain_id, + annotations=annotation_files, + merged_annotation=project_annotations_file_desc, + manifest=manifest, + project_images=cvat_service.get_project_images(session, project.cvat_id), + ) + + annotation_files.append(annotation_metafile) + + storage_client = cloud_client.S3Client( + StorageConfig.provider_endpoint_url(), + access_key=StorageConfig.access_key, + secret_key=StorageConfig.secret_key, + ) + existing_storage_files = set( + f.key + for f in storage_client.list_files( + StorageConfig.data_bucket_name, + prefix=compose_results_bucket_filename( + project.escrow_address, + project.chain_id, + "", + ), + ) + ) + for file_descriptor in annotation_files: + if file_descriptor.filename in existing_storage_files: + continue + + storage_client.create_file( + StorageConfig.data_bucket_name, + compose_results_bucket_filename( + project.escrow_address, + project.chain_id, + file_descriptor.filename, + ), + file_descriptor.file.read(), + ) + + self._notify_escrow_completed( + project.escrow_address, project.chain_id, db_session=session + ) + + cvat_service.update_project_status(session, project.id, ProjectStatuses.validation) + + logger.info( + f"The project (escrow_address={project.escrow_address}) " + "is completed, resulting annotations are processed successfully" + ) + + def _notify_escrow_completed(self, escrow_address: str, chain_id: int, *, db_session: Session): + oracle_db_service.outbox.create_webhook( + db_session, + escrow_address, + chain_id, + OracleWebhookTypes.recording_oracle, + event=ExchangeOracleEvent_TaskFinished(), + ) + + def _process_skeletons_from_boxes_escrows(self): + logger = self.logger + + # Here we can have several projects per escrow, so the handling is done in project groups + with SessionLocal.begin() as session: + completed_projects = cvat_service.get_projects_by_status( + session, + ProjectStatuses.completed, + included_types=[TaskType.image_skeletons_from_boxes], + limit=CronConfig.retrieve_annotations_chunk_size, + ) + + escrows_with_completed_projects = set() + for completed_project in completed_projects: + # Check if all jobs within the project are completed + if not cvat_service.is_project_completed(session, completed_project.id): + cvat_service.update_project_status( + session, completed_project.id, ProjectStatuses.annotation + ) + continue + + try: + # TODO: such escrows can fill all the queried completed projects + # need to improve handling for such projects + # (e.g. cancel depending on the escrow status) + validate_escrow(completed_project.chain_id, completed_project.escrow_address) + except Exception as e: + logger.error( + "Failed to handle completed projects for escrow {}: {}".format( + escrow_address, e + ) + ) + continue + + escrows_with_completed_projects.add( + (completed_project.escrow_address, completed_project.chain_id) + ) + + for escrow_address, chain_id in escrows_with_completed_projects: + # TODO: should throw a db lock exception if lock is not available + # need to skip the escrow in this case. + # Maybe there is a better way that utilizes skip_locked + try: + escrow_projects = cvat_service.get_projects_by_escrow_address( + session, escrow_address, limit=None, for_update=ForUpdateParams(nowait=True) + ) + except db_exc.OperationalError as ex: + if "could not obtain lock on row" in str(ex): + continue + raise + + completed_escrow_projects = [ + p + for p in escrow_projects + if p.status + in [ + ProjectStatuses.completed, + ProjectStatuses.validation, # TODO: think about this list + ] + ] + if len(escrow_projects) != len(completed_escrow_projects): + continue + + manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) + + logger.debug( + f"Downloading results for the escrow (escrow_address={escrow_address})" + ) + + cvat_jobs: List[cvat_models.Job] = list( + itertools.chain.from_iterable( + cvat_service.get_jobs_by_cvat_project_id(session, p.cvat_id) + for p in escrow_projects + ) + ) + + # Request dataset preparation beforehand + annotation_format = CVAT_EXPORT_FORMAT_MAPPING[manifest.annotation.type] + for cvat_job in cvat_jobs: + cvat_api.request_job_annotations( + cvat_job.cvat_id, format_name=annotation_format + ) + + # Collect raw annotations from CVAT, validate and convert them + # into a recording oracle suitable format + job_annotations: Dict[int, FileDescriptor] = {} + for cvat_job in cvat_jobs: + job_annotations_file = cvat_api.get_job_annotations( + cvat_job.cvat_id, format_name=annotation_format + ) + job_assignment = cvat_job.latest_assignment + job_annotations[cvat_job.cvat_id] = FileDescriptor( + filename="project_{}-task_{}-job_{}-user_{}-assignment_{}.zip".format( + cvat_job.cvat_project_id, + cvat_job.cvat_task_id, + cvat_job.cvat_id, + job_assignment.user.cvat_id, + job_assignment.id, + ), + file=job_annotations_file, + ) + + resulting_annotations_file_desc = FileDescriptor( + filename=RESULTING_ANNOTATIONS_FILE, + file=None, + ) + + annotation_files: List[FileDescriptor] = [] + annotation_files.append(resulting_annotations_file_desc) + + annotation_metafile = prepare_annotation_metafile( + jobs=cvat_jobs, job_annotations=job_annotations + ) + annotation_files.extend(job_annotations.values()) + postprocess_annotations( + escrow_address=escrow_address, + chain_id=chain_id, + annotations=annotation_files, + merged_annotation=resulting_annotations_file_desc, + manifest=manifest, + project_images=None, + ) + + annotation_files.append(annotation_metafile) + + storage_client = cloud_client.S3Client( + StorageConfig.provider_endpoint_url(), + access_key=StorageConfig.access_key, + secret_key=StorageConfig.secret_key, + ) + existing_storage_files = set( + f.key + for f in storage_client.list_files( + StorageConfig.data_bucket_name, + prefix=compose_results_bucket_filename( + escrow_address, + chain_id, + "", + ), + ) + ) + for file_descriptor in annotation_files: + if file_descriptor.filename in existing_storage_files: + continue + + storage_client.create_file( + StorageConfig.data_bucket_name, + compose_results_bucket_filename( + escrow_address, + chain_id, + file_descriptor.filename, + ), + file_descriptor.file.read(), + ) + + self._notify_escrow_completed(escrow_address, chain_id, db_session=session) + + for project in escrow_projects: + cvat_service.update_project_status( + session, project.id, ProjectStatuses.validation + ) + + logger.info( + f"The escrow (escrow_address={project.escrow_address}) " + "is completed, resulting annotations are processed successfully" + ) + + def process(self): + self._process_plain_escrows() + self._process_skeletons_from_boxes_escrows() + + +def handle_completed_escrows(logger: logging.Logger) -> None: + handler = _CompletedEscrowsHandler(logger=logger) + handler.process() diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 09b8cf4017..a525a4b7c9 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -5,7 +5,7 @@ import uuid from contextlib import ExitStack from dataclasses import dataclass -from itertools import groupby, product +from itertools import chain, groupby from logging import Logger from math import ceil from tempfile import TemporaryDirectory @@ -32,6 +32,7 @@ from src.log import ROOT_LOGGER_NAME from src.services.cloud import CloudProviders, StorageClient from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url +from src.utils.annotations import ProjectLabels from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger @@ -246,7 +247,7 @@ def _validate_gt_labels(self): ) self.input_gt_dataset.transform( - "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) self.input_gt_dataset.init_cache() @@ -323,7 +324,7 @@ def _validate_points_categories(self): self.input_points_dataset.transform( "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] - ) + ) # TODO: fix the transform for skeletons self.input_points_dataset.init_cache() def _validate_points_filenames(self): @@ -703,7 +704,9 @@ def _upload_task_meta(self): serializer = boxes_from_points_task.TaskMetaSerializer() file_list = [] - file_list.append((self.input_points_data, layout.POINTS_FILENAME)) + file_list.append( + (self.input_points_data, layout.POINTS_FILENAME) + ) # TODO: save cleaned version as well file_list.append( ( serializer.serialize_gt_annotations(self.gt_dataset), @@ -947,8 +950,8 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): # Computed values self.input_filenames: _MaybeUnset[Sequence[str]] = _unset - self.input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset - self.input_boxes_dataset: _MaybeUnset[dm.Dataset] = _unset + self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self.boxes_dataset: _MaybeUnset[dm.Dataset] = _unset self.roi_filenames: _MaybeUnset[Dict[int, str]] = _unset self.job_params: _MaybeUnset[List[self._JobParams]] = _unset @@ -1042,7 +1045,7 @@ def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm def _parse_gt(self): assert self.input_gt_data is not _unset - self.input_gt_dataset = self._parse_dataset( + self.gt_dataset = self._parse_dataset( self.input_gt_data, dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], ) @@ -1050,14 +1053,14 @@ def _parse_gt(self): def _parse_boxes(self): assert self.input_boxes_data is not _unset - self.input_boxes_dataset = self._parse_dataset( + self.boxes_dataset = self._parse_dataset( self.input_boxes_data, dataset_format=self.boxes_format ) def _validate_gt_labels(self): gt_labels = set( (label.name, label.parent) - for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + for label in self.gt_dataset.categories()[dm.AnnotationType.label] ) manifest_labels = set() @@ -1078,14 +1081,14 @@ def _validate_gt_labels(self): ) ) - # Reorder labels to match manifest - self.input_gt_dataset.transform( - "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + # Reorder labels to match the manifest + self.gt_dataset.transform( + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self.input_gt_dataset.init_cache() + self.gt_dataset.init_cache() def _validate_gt_filenames(self): - gt_filenames = set(s.id + s.media.ext for s in self.input_gt_dataset) + gt_filenames = set(s.id + s.media.ext for s in self.gt_dataset) known_data_filenames = set(self.input_filenames) matched_gt_filenames = gt_filenames.intersection(known_data_filenames) @@ -1107,7 +1110,7 @@ def _validate_gt_filenames(self): def _validate_gt(self): assert self.input_filenames is not _unset - assert self.input_gt_dataset is not _unset + assert self.gt_dataset is not _unset self._validate_gt_filenames() self._validate_gt_labels() @@ -1117,7 +1120,7 @@ def _validate_gt(self): # TODO: pass discarded/total down def _validate_boxes_categories(self): - boxes_dataset_categories = self.input_boxes_dataset.categories() + boxes_dataset_categories = self.boxes_dataset.categories() boxes_dataset_label_cat: dm.LabelCategories = boxes_dataset_categories[ dm.AnnotationType.label ] @@ -1127,14 +1130,14 @@ def _validate_boxes_categories(self): if manifest_labels != boxes_labels: raise DatasetValidationError("Bbox labels do not match job labels") - # Reorder labels to match manifest - self.input_boxes_dataset.transform( - "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] + # Reorder labels to match the manifest + self.boxes_dataset.transform( + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self.input_boxes_dataset.init_cache() + self.boxes_dataset.init_cache() def _validate_boxes_filenames(self): - boxes_filenames = set(sample.id + sample.media.ext for sample in self.input_boxes_dataset) + boxes_filenames = set(sample.id + sample.media.ext for sample in self.boxes_dataset) known_data_filenames = set(self.input_filenames) matched_boxes_filenames = boxes_filenames.intersection(known_data_filenames) @@ -1157,13 +1160,11 @@ def _validate_boxes_filenames(self): ) def _validate_boxes_annotations(self): - label_cat: dm.LabelCategories = self.input_boxes_dataset.categories()[ - dm.AnnotationType.label - ] + label_cat: dm.LabelCategories = self.boxes_dataset.categories()[dm.AnnotationType.label] # TODO: check for excluded boxes count excluded_samples = [] - for sample in self.input_boxes_dataset: + for sample in self.boxes_dataset: # Could fail on this as well image_h, image_w = sample.image.size @@ -1181,7 +1182,7 @@ def _validate_boxes_annotations(self): ) excluded_samples.append(((sample.id, sample.subset), message)) - if len(excluded_samples) > len(self.input_boxes_dataset) * self.max_discarded_threshold: + if len(excluded_samples) > len(self.boxes_dataset) * self.max_discarded_threshold: raise DatasetValidationError( "Too many samples discarded, canceling job creation. Errors: {}".format( self._format_list([message for _, message in excluded_samples]) @@ -1189,7 +1190,7 @@ def _validate_boxes_annotations(self): ) for excluded_sample, _ in excluded_samples: - self.input_boxes_dataset.remove(*excluded_sample) + self.boxes_dataset.remove(*excluded_sample) if excluded_samples: self.logger.warning( @@ -1200,7 +1201,7 @@ def _validate_boxes_annotations(self): def _validate_boxes(self): assert self.input_filenames is not _unset - assert self.input_boxes_dataset is not _unset + assert self.boxes_dataset is not _unset self._validate_boxes_categories() self._validate_boxes_filenames() @@ -1223,32 +1224,32 @@ def _match_boxes(self, a: BboxCoords, b: BboxCoords): def _prepare_gt(self): assert self.input_filenames is not _unset - assert self.input_boxes_dataset is not _unset - assert self.input_gt_dataset is not _unset + assert self.boxes_dataset is not _unset + assert self.gt_dataset is not _unset assert [ label.name - for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + for label in self.gt_dataset.categories()[dm.AnnotationType.label] if not label.parent ] == [label.name for label in self.manifest.annotation.labels] assert [ label.name - for label in self.input_boxes_dataset.categories()[dm.AnnotationType.label] + for label in self.boxes_dataset.categories()[dm.AnnotationType.label] if not label.parent ] == [label.name for label in self.manifest.annotation.labels] - gt_dataset = dm.Dataset(categories=self.input_gt_dataset.categories(), media_type=dm.Image) + updated_gt_dataset = dm.Dataset( + categories=self.gt_dataset.categories(), media_type=dm.Image + ) - gt_label_cat: dm.LabelCategories = self.input_gt_dataset.categories()[ - dm.AnnotationType.label - ] + gt_label_cat: dm.LabelCategories = self.gt_dataset.categories()[dm.AnnotationType.label] excluded_skeletons_messages = [] total_skeletons = 0 gt_count_per_class = {} skeleton_bbox_mapping = {} # skeleton id -> bbox id - for gt_sample in self.input_gt_dataset: - boxes_sample = self.input_boxes_dataset.get(gt_sample.id, gt_sample.subset) + for gt_sample in self.gt_dataset: + boxes_sample = self.boxes_dataset.get(gt_sample.id, gt_sample.subset) # Samples could be discarded, so we just skip them without an error if not boxes_sample: continue @@ -1331,7 +1332,7 @@ def _prepare_gt(self): if not matched_skeletons: continue - gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) + updated_gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) if len(skeleton_bbox_mapping) < (1 - self.max_discarded_threshold) * total_skeletons: raise DatasetValidationError( @@ -1355,15 +1356,15 @@ def _prepare_gt(self): ) ) - self.gt_dataset = gt_dataset + self.gt_dataset = updated_gt_dataset self.skeleton_bbox_mapping = skeleton_bbox_mapping def _prepare_roi_infos(self): assert self.gt_dataset is not _unset - assert self.input_boxes_dataset is not _unset + assert self.boxes_dataset is not _unset rois: List[skeletons_from_boxes_task.RoiInfo] = [] - for sample in self.input_boxes_dataset: + for sample in self.boxes_dataset: for bbox in sample.annotations: if not isinstance(bbox, dm.Bbox): continue @@ -1378,8 +1379,8 @@ def _prepare_roi_infos(self): roi_x = original_bbox_cx - int(roi_w / 2) roi_y = original_bbox_cy - int(roi_h / 2) - new_bbox_x = original_bbox_cx - roi_x - new_bbox_y = original_bbox_cy - roi_y + new_bbox_x = bbox.x - roi_x + new_bbox_y = bbox.y - roi_y rois.append( skeletons_from_boxes_task.RoiInfo( @@ -1436,6 +1437,7 @@ def _prepare_job_params(self): label_data_roi_ids = [ roi_info.bbox_id for roi_info in self.roi_infos + if roi_info.bbox_label == label_id if roi_info.bbox_id not in label_gt_roi_ids ] random.shuffle(label_data_roi_ids) @@ -1457,10 +1459,52 @@ def _prepare_job_params(self): self.job_params = job_params + def _prepare_job_labels(self): + self.point_labels = {} + + for skeleton_label in self.manifest.annotation.labels: + for point_name in skeleton_label.nodes: + self.point_labels[(skeleton_label.name, point_name)] = point_name + def _upload_task_meta(self): - # TODO: - # raise NotImplementedError - pass + # TODO: maybe extract into a separate function / class / library, + # extract constants, serialization methods return TaskConfig from build() + + layout = skeletons_from_boxes_task.TaskMetaLayout() + serializer = skeletons_from_boxes_task.TaskMetaSerializer() + + file_list = [] + file_list.append( + (serializer.serialize_bbox_annotations(self.boxes_dataset), layout.BOXES_FILENAME) + ) + file_list.append( + ( + serializer.serialize_gt_annotations(self.gt_dataset), + layout.GT_FILENAME, + ) + ) + file_list.append( + ( + serializer.serialize_skeleton_bbox_mapping(self.skeleton_bbox_mapping), + layout.SKELETON_BBOX_MAPPING_FILENAME, + ) + ) + file_list.append((serializer.serialize_roi_info(self.roi_infos), layout.ROI_INFO_FILENAME)) + file_list.append( + (serializer.serialize_roi_filenames(self.roi_filenames), layout.ROI_FILENAMES_FILENAME) + ) + file_list.append( + (serializer.serialize_point_labels(self.point_labels), layout.POINT_LABELS_FILENAME) + ) + + storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) + bucket_name = self.oracle_data_bucket.url.bucket_name + for file_data, filename in file_list: + storage_client.create_file( + bucket_name, + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), + file_data, + ) def _extract_roi( self, source_pixels: np.ndarray, roi_info: skeletons_from_boxes_task.RoiInfo @@ -1519,10 +1563,10 @@ def _extract_and_upload_rois(self): dst_client = self._make_cloud_storage_client(dst_bucket) image_id_to_filename = { - sample.attributes["id"]: sample.image.path for sample in self.input_boxes_dataset + sample.attributes["id"]: sample.image.path for sample in self.boxes_dataset } - filename_to_sample = {sample.image.path: sample for sample in self.input_boxes_dataset} + filename_to_sample = {sample.image.path: sample for sample in self.boxes_dataset} _roi_info_key = lambda e: e.original_image_key roi_info_by_image: Dict[str, Sequence[skeletons_from_boxes_task.RoiInfo]] = { @@ -1532,7 +1576,7 @@ def _extract_and_upload_rois(self): bbox_by_id = { bbox.id: bbox - for sample in self.input_boxes_dataset + for sample in self.boxes_dataset for bbox in sample.annotations if isinstance(bbox, dm.Bbox) } @@ -1575,12 +1619,12 @@ def _extract_and_upload_rois(self): def _create_on_cvat(self): assert self.job_params is not _unset + assert self.point_labels is not _unset - # TODO: Find a way to handle different labels in tasks in a project _job_params_label_key = lambda ts: ts.label_id - jobs_by_label = { - label_id: list(g) - for label_id, g in groupby( + jobs_by_skeleton_label = { + skeleton_label_id: list(g) + for skeleton_label_id, g in groupby( sorted(self.job_params, key=_job_params_label_key), key=_job_params_label_key ) } @@ -1588,12 +1632,10 @@ def _create_on_cvat(self): label_specs_by_skeleton = { skeleton_label_id: [ { - "name": point_name - if len(self.manifest.annotation.labels) == 1 - else f"{skeleton_label}.{point_name}", + "name": self.point_labels[(skeleton_label.name, skeleton_point)], "type": "points", } - for point_name in skeleton_label.nodes + for skeleton_point in skeleton_label.nodes ] for skeleton_label_id, skeleton_label in enumerate(self.manifest.annotation.labels) } @@ -1614,66 +1656,71 @@ def _create_on_cvat(self): # credentials=... ) - for label_id, label_jobs in jobs_by_label.items(): - # Create a project. CVAT doesn't support tasks with different labels in a project - project = cvat_api.create_project( - self.escrow_address, - user_guide=self.manifest.annotation.user_guide, - # TODO: improve guide handling - split for different points - ) - - # Setup webhooks for a project (update:task, update:job) - webhook = cvat_api.create_cvat_webhook(project.id) - - with SessionLocal.begin() as session: - db_service.create_project( - session, - project.id, - cloud_storage.id, - self.manifest.annotation.type, - self.escrow_address, - self.chain_id, - compose_bucket_url( - input_data_bucket.url.bucket_name, - bucket_host=input_data_bucket.url.host_url, - provider=input_data_bucket.url.provider, - ), - cvat_webhook_id=webhook.id, - ) - db_service.add_project_images( - session, - project.id, - [ - compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) - for fn in self.roi_filenames.values() - ], - ) - - job_filenames_map = [] - for label_job in label_jobs: - job_filenames_map.append( + for skeleton_label_id, skeleton_label_jobs in jobs_by_skeleton_label.items(): + # Each skeleton point uses the same file layout in jobs + skeleton_label_filenames = [] + for skeleton_label_job in skeleton_label_jobs: + skeleton_label_filenames.append( [ compose_data_bucket_filename( self.escrow_address, self.chain_id, self.roi_filenames[roi_id] ) - for roi_id in label_job.roi_ids + for roi_id in skeleton_label_job.roi_ids ] ) - point_label_specs = label_specs_by_skeleton[label_id] - for label_spec in point_label_specs: - for job_filenames in job_filenames_map: - task = cvat_api.create_task(project.id, self.escrow_address, labels=[label_spec]) + for point_label_spec in label_specs_by_skeleton[skeleton_label_id]: + # Create a project for each point label. + # CVAT doesn't support tasks with different labels in a project. + project = cvat_api.create_project( + name="{} ({} {})".format( + self.escrow_address, + self.manifest.annotation.labels[skeleton_label_id].name, + point_label_spec["name"], + ), + user_guide=self.manifest.annotation.user_guide, + labels=[point_label_spec], + # TODO: improve guide handling - split for different points + ) + + # Setup webhooks for a project (update:task, update:job) + webhook = cvat_api.create_cvat_webhook(project.id) + + with SessionLocal.begin() as session: + db_service.create_project( + session, + project.id, + cloud_storage.id, + self.manifest.annotation.type, + self.escrow_address, + self.chain_id, + compose_bucket_url( + input_data_bucket.url.bucket_name, + bucket_host=input_data_bucket.url.host_url, + provider=input_data_bucket.url.provider, + ), + cvat_webhook_id=webhook.id, + ) + db_service.add_project_images( + session, + project.id, + list(set(chain.from_iterable(skeleton_label_filenames))), + ) + + for point_label_filenames in skeleton_label_filenames: + task = cvat_api.create_task(project.id, name=project.name) with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + db_service.create_task( + session, task.id, project.id, TaskStatus[task.status] + ) # Actual task creation in CVAT takes some time, so it's done in an async process. # The task will be created in DB once 'update:task' or 'update:job' webhook is received. cvat_api.put_task_data( task.id, cloud_storage.id, - filenames=job_filenames, + filenames=point_label_filenames, sort_images=False, ) @@ -1696,6 +1743,7 @@ def build(self): self._prepare_roi_infos() self._prepare_job_params() self._mangle_filenames() + self._prepare_job_labels() # Data preparation self._extract_and_upload_rois() diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index ac6966908f..65048552eb 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -1,14 +1,15 @@ import io import os import zipfile +from dataclasses import dataclass from tempfile import TemporaryDirectory -from typing import Dict, List, Type +from typing import Dict, List, Optional, Type import datumaro as dm -from attrs import define from datumaro.components.dataset import Dataset import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.core.tasks.skeletons_from_boxes as skeletons_from_boxes_task import src.utils.annotations as annotation_utils from src.core.annotation_meta import ANNOTATION_RESULTS_METAFILE_NAME, AnnotationMeta, JobMeta from src.core.config import Config @@ -26,6 +27,7 @@ TaskType.image_points: "CVAT for images 1.1", TaskType.image_boxes: "COCO 1.0", TaskType.image_boxes_from_points: "COCO 1.0", + TaskType.image_skeletons_from_boxes: "CVAT for images 1.1", } CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { @@ -34,10 +36,10 @@ } -@define +@dataclass class FileDescriptor: filename: str - file: io.RawIOBase + file: Optional[io.RawIOBase] def prepare_annotation_metafile( @@ -75,8 +77,8 @@ def __init__( ): self.escrow_address = escrow_address self.chain_id = chain_id - self.annotations = annotations - self.merged_annotation = merged_annotation + self.annotation_files = annotations + self.merged_annotation_file = merged_annotation self.manifest = manifest self.project_images = project_images @@ -85,9 +87,12 @@ def __init__( ] self.output_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + def _is_merged_dataset(self, ann_descriptor: FileDescriptor) -> bool: + return ann_descriptor == self.merged_annotation_file + def process(self): with TemporaryDirectory() as tempdir: - for ann_descriptor in self.annotations: + for ann_descriptor in self.annotation_files: if not zipfile.is_zipfile(ann_descriptor.file): raise ValueError("Annotation files must be zip files") ann_descriptor.file.seek(0) @@ -129,7 +134,7 @@ def _process_dataset( ) -> dm.Dataset: # TODO: remove complete duplicates in annotations - if ann_descriptor.filename == self.merged_annotation.filename: + if self._is_merged_dataset(ann_descriptor): dataset = self._process_merged_dataset(dataset) return dataset @@ -268,6 +273,239 @@ def _process_merged_dataset(self, input_dataset: Dataset) -> Dataset: return merged_sample_dataset +class _SkeletonsFromBoxesTaskProcessor(_TaskProcessor): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + roi_filenames, roi_infos, boxes_dataset, job_label_mapping = self._download_task_meta() + + self.boxes_dataset = boxes_dataset + self.original_key_to_sample = {sample.attributes["id"]: sample for sample in boxes_dataset} + + roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in roi_infos} + + self.roi_name_to_roi_info: Dict[str, skeletons_from_boxes_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self.job_label_mapping = job_label_mapping + + def _download_task_meta(self): + layout = skeletons_from_boxes_task.TaskMetaLayout() + serializer = skeletons_from_boxes_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.from_raw_url(Config.storage_config.bucket_url()) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + storage_client = make_cloud_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + boxes_dataset = serializer.parse_bbox_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BOXES_FILENAME + ), + ) + ) + + job_label_mapping = serializer.parse_point_labels( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINT_LABELS_FILENAME + ), + ) + ) + + return roi_filenames, rois, boxes_dataset, job_label_mapping + + def _init_merged_dataset(self): + label_cat = dm.LabelCategories() + points_cat = dm.PointsCategories() + + # Maintain label order for simplicity + for manifest_skeleton in self.manifest.annotation.labels: + label_cat.add(manifest_skeleton.name) + + for manifest_skeleton in self.manifest.annotation.labels: + points_cat.add( + label_cat.find(manifest_skeleton.name)[0], + labels=manifest_skeleton.nodes, + joints=manifest_skeleton.joints, + ) + + for manifest_skeleton_point in manifest_skeleton.nodes: + label_cat.add(manifest_skeleton_point, parent=manifest_skeleton.name) + + self.job_point_label_id_to_merged_label_id = { + (label_cat.find(skeleton_label)[0], job_point_label): label_cat.find( + point_label, parent=skeleton_label + )[0] + for (skeleton_label, point_label), job_point_label in self.job_label_mapping.items() + } + + self.merged_dataset = dm.Dataset( + categories={ + dm.AnnotationType.label: label_cat, + dm.AnnotationType.points: points_cat, + }, + media_type=dm.Image, + ) + + bbox_label_to_merged = { + bbox_label_id: label_cat.find(bbox_label.name)[0] + for bbox_label_id, bbox_label in enumerate( + self.boxes_dataset.categories()[dm.AnnotationType.label] + ) + } + self.bbox_label_to_merged = bbox_label_to_merged + + for bbox_sample in self.boxes_dataset: + self.merged_dataset.put( + bbox_sample.wrap( + annotations=[ + dm.Skeleton( + elements=[ + dm.Points( + [0, 0], + visibility=[dm.Points.Visibility.hidden], + label=label_cat.find( + point_label, + parent=label_cat[bbox_label_to_merged[bbox.label]].name, + )[0], + ) + for point_label in points_cat[ + bbox_label_to_merged[bbox.label] + ].labels + ], + label=bbox_label_to_merged[bbox.label], + id=bbox.id, + ) + for bbox in bbox_sample.annotations + if isinstance(bbox, dm.Bbox) + ] + ) + ) + + def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> Dataset: + annotation_utils.prepare_cvat_annotations_for_dm(dataset_dir) + return super()._parse_dataset(ann_descriptor, dataset_dir) + + def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) -> Dataset: + # We need to convert point arrays, which cannot be represented in COCO directly, + # into the 1-point skeletons, compatible with COCO person keypoints, which is the + # required output format + + job_label_cat = dataset.categories()[dm.AnnotationType.label] + + for point_sample in dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(point_sample.id)] + + bbox_sample = self.original_key_to_sample[roi_info.original_image_key] + assert bbox_sample + + skeleton_sample = self.merged_dataset.get(bbox_sample.id) + assert skeleton_sample + + image_h, image_w = bbox_sample.image.size + + old_bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + offset_x = old_bbox.x - roi_info.bbox_x + offset_y = old_bbox.y - roi_info.bbox_y + + skeleton = next( + ann + for ann in skeleton_sample.annotations + if isinstance(ann, dm.Skeleton) + if ann.id == roi_info.bbox_id and isinstance(ann, dm.Skeleton) + ) + + # TODO: think about discarding invalid annotations (points) + # For now, just take the first one available, as only 1 must be put by annotators + point_sample_points = annotation_utils.flatten_points( + [p for p in point_sample.annotations if isinstance(p, dm.Points)] + ) + if not point_sample_points: + continue + + annotated_point = point_sample_points[0] + + skeleton_point_idx, skeleton_point = next( + (p_idx, p) + for p_idx, p in enumerate(skeleton.elements) + if p.label + == self.job_point_label_id_to_merged_label_id[ + (roi_info.bbox_label, job_label_cat[annotated_point.label].name) + ] + ) + + skeleton_point.points[:2] = annotated_point.points[:2] + skeleton_point.visibility[0] = annotated_point.visibility[0] + + skeleton.elements[skeleton_point_idx] = annotation_utils.shift_ann( + skeleton_point, offset_x=offset_x, offset_y=offset_y, img_h=image_h, img_w=image_w + ) + + return super()._process_dataset(dataset, ann_descriptor=ann_descriptor) + + def _process_merged_dataset(self, merged_dataset: Dataset) -> Dataset: + return merged_dataset + + def process(self): + assert self.merged_annotation_file.file is None + + # Accumulate points from separate job annotations, then export the resulting dataset + self._init_merged_dataset() + + self.annotation_files = [ + fd for fd in self.annotation_files if not self._is_merged_dataset(fd) + ] + super().process() + self.annotation_files.append(self.merged_annotation_file) + + with TemporaryDirectory() as tempdir: + export_dir = os.path.join( + tempdir, + os.path.splitext(os.path.basename(self.merged_annotation_file.filename))[0] + + "_conv", + ) + + merged_dataset = self._process_merged_dataset(self.merged_dataset) + self._export_dataset(merged_dataset, export_dir) + + converted_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(export_dir, converted_dataset_archive) + converted_dataset_archive.seek(0) + + self.merged_annotation_file.file = converted_dataset_archive + + def postprocess_annotations( escrow_address: str, chain_id: int, @@ -285,6 +523,7 @@ def postprocess_annotations( TaskType.image_boxes: _BoxesTaskProcessor, TaskType.image_points: _PointsTaskProcessor, TaskType.image_boxes_from_points: _BoxesFromPointsTaskProcessor, + TaskType.image_skeletons_from_boxes: _SkeletonsFromBoxesTaskProcessor, } task_type = manifest.annotation.type diff --git a/packages/examples/cvat/exchange-oracle/src/models/cvat.py b/packages/examples/cvat/exchange-oracle/src/models/cvat.py index 2ffed4ccd6..e8a5d1672b 100644 --- a/packages/examples/cvat/exchange-oracle/src/models/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/models/cvat.py @@ -26,7 +26,9 @@ class Project(Base): cvat_cloudstorage_id = Column(Integer, index=True, nullable=False) status = Column(String, Enum(ProjectStatuses), nullable=False) job_type = Column(String, Enum(TaskType), nullable=False) - escrow_address = Column(String(42), unique=True, nullable=False) + escrow_address = Column( + String(42), unique=False, nullable=False + ) # TODO: extract into a separate model chain_id = Column(Integer, Enum(Networks), nullable=False) bucket_url = Column(String, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cvat.py b/packages/examples/cvat/exchange-oracle/src/services/cvat.py index fd78b5ecd8..1d802b8a36 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cvat.py @@ -1,11 +1,11 @@ import uuid from datetime import datetime -from typing import List, Optional, Union +from typing import List, Optional, Sequence, Union from sqlalchemy import delete, insert, update from sqlalchemy.orm import Session -from src.core.types import AssignmentStatus, JobStatuses, ProjectStatuses, TaskStatus +from src.core.types import AssignmentStatus, JobStatuses, ProjectStatuses, TaskStatus, TaskType from src.db.utils import ForUpdateParams from src.db.utils import maybe_for_update as _maybe_for_update from src.models.cvat import Assignment, DataUpload, Image, Job, Project, Task, User @@ -73,19 +73,40 @@ def get_project_by_escrow_address( ) +def get_projects_by_escrow_address( + session: Session, + escrow_address: str, + *, + for_update: Union[bool, ForUpdateParams] = False, + limit: Optional[int] = 5, +) -> List[Project]: + projects = _maybe_for_update(session.query(Project), enable=for_update).where( + Project.escrow_address == escrow_address + ) + + if limit is not None: + projects = projects.limit(limit) + + return projects.all() + + def get_projects_by_status( session: Session, status: ProjectStatuses, *, + included_types: Optional[Sequence[TaskType]] = None, limit: int = 5, for_update: Union[bool, ForUpdateParams] = False, ) -> List[Project]: - projects = ( - _maybe_for_update(session.query(Project), enable=for_update) - .where(Project.status == status.value) - .limit(limit) - .all() + projects = _maybe_for_update(session.query(Project), enable=for_update).where( + Project.status == status.value ) + + if included_types is not None: + projects = projects.where(Project.job_type.in_([t.value for t in included_types])) + + projects = projects.limit(limit).all() + return projects diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py index 4edf05609f..6035876f2b 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -1,9 +1,11 @@ import os +from copy import deepcopy from glob import glob -from typing import Dict, List, Sequence +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, Union import datumaro as dm import numpy as np +from datumaro.util import filter_dict, mask_tools from defusedxml import ElementTree as ET @@ -14,7 +16,21 @@ def flatten_points(input_points: Sequence[dm.Points]) -> List[dm.Points]: for point_idx in range(len(pts.points) // 2): point_x = pts.points[2 * point_idx + 0] point_y = pts.points[2 * point_idx + 1] - results.append(dm.Points([point_x, point_y], label=pts.label)) + + point_v = pts.visibility[point_idx] + if pts.attributes.get("outside") is True: + point_v = dm.Points.Visibility.absent + elif point_v == dm.Points.Visibility.visible and pts.attributes.get("occluded") is True: + point_v = dm.Points.Visibility.hidden + + results.append( + dm.Points( + [point_x, point_y], + visibility=[point_v], + label=pts.label, + attributes=filter_dict(pts.attributes, exclude_keys=["occluded", "outside"]), + ) + ) return results @@ -60,7 +76,7 @@ def convert_point_arrays_dataset_to_1_point_skeletons( ) -> dm.Dataset: """ In the COCO Person Keypoints format, we can only represent points inside skeletons. - The function converts annotations from points to skeletons in the dataset. + The function converts annotations from points to 1-point skeletons in the dataset. """ def _get_skeleton_label(original_label: str) -> str: @@ -158,3 +174,166 @@ def shift_ann( assert False, f"Unsupported annotation type '{ann.type}'" return shifted_ann + + +class ProjectLabels(dm.ItemTransform): + """ + Changes the order of labels in the dataset from the existing + to the desired one, removes unknown labels and adds new labels. + Updates or removes the corresponding annotations.|n + |n + Labels are matched by names (case dependent). Parent labels are only kept + if they are present in the resulting set of labels. If new labels are + added, and the dataset has mask colors defined, new labels will obtain + generated colors.|n + |n + Useful for merging similar datasets, whose labels need to be aligned.|n + |n + Examples:|n + |s|s- Align the source dataset labels to [person, cat, dog]:|n + + |s|s.. code-block:: + + |s|s|s|s%(prog)s -l person -l cat -l dog + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument( + "-l", + "--label", + action="append", + dest="dst_labels", + help="Label name (repeatable, ordered)", + ) + return parser + + def __init__( + self, + extractor: dm.IExtractor, + dst_labels: Union[Iterable[Union[str, Tuple[str, str]]], dm.LabelCategories], + ): + super().__init__(extractor) + + self._categories = {} + + src_categories = self._extractor.categories() + + src_label_cat: Optional[dm.LabelCategories] = src_categories.get(dm.AnnotationType.label) + src_point_cat: Optional[dm.PointsCategories] = src_categories.get(dm.AnnotationType.points) + + if isinstance(dst_labels, dm.LabelCategories): + dst_label_cat = deepcopy(dst_labels) + else: + dst_labels = list(dst_labels) + + if src_label_cat: + dst_label_cat = dm.LabelCategories(attributes=deepcopy(src_label_cat.attributes)) + + for dst_label in dst_labels: + assert isinstance(dst_label, str) or isinstance(dst_label, tuple) + + dst_parent = "" + if isinstance(dst_label, tuple): + dst_label, dst_parent = dst_label + + src_label = src_label_cat.find(dst_label, dst_parent)[1] + if src_label is not None: + dst_label_cat.add( + dst_label, src_label.parent, deepcopy(src_label.attributes) + ) + else: + dst_label_cat.add(dst_label, dst_parent) + else: + dst_label_cat = dm.LabelCategories.from_iterable(dst_labels) + + for label in dst_label_cat: + if label.parent not in dst_label_cat: + label.parent = "" + + if src_point_cat: + # Copy nested labels + for skeleton_label_id, skeleton_spec in src_point_cat.items.items(): + skeleton_label = src_label_cat[skeleton_label_id] + for child_label_name in skeleton_spec.labels: + if ( + skeleton_label.name in dst_label_cat + and not dst_label_cat.find(child_label_name, parent=skeleton_label.name)[1] + ): + dst_label_cat.add( + child_label_name, + parent=skeleton_label.name, + attributes=skeleton_label.attributes, + ) + + self._categories[dm.AnnotationType.label] = dst_label_cat + + self._make_label_id_map(src_label_cat, dst_label_cat) + + src_mask_cat = src_categories.get(dm.AnnotationType.mask) + if src_mask_cat is not None: + assert src_label_cat is not None + dst_mask_cat = dm.MaskCategories(attributes=deepcopy(src_mask_cat.attributes)) + for old_id, old_color in src_mask_cat.colormap.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_mask_cat: + dst_mask_cat.colormap[new_id] = deepcopy(old_color) + + # Generate new colors for new labels, keep old untouched + existing_colors = set(dst_mask_cat.colormap.values()) + color_bank = iter( + mask_tools.generate_colormap(len(dst_label_cat), include_background=False).values() + ) + for new_id, new_label in enumerate(dst_label_cat): + if new_label.name in src_label_cat: + continue + if new_id in dst_mask_cat: + continue + + color = next(color_bank) + while color in existing_colors: + color = next(color_bank) + + dst_mask_cat.colormap[new_id] = color + + self._categories[dm.AnnotationType.mask] = dst_mask_cat + + if src_point_cat is not None: + assert src_label_cat is not None + dst_point_cat = dm.PointsCategories(attributes=deepcopy(src_point_cat.attributes)) + for old_id, old_cat in src_point_cat.items.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_point_cat: + dst_point_cat.items[new_id] = deepcopy(old_cat) + + self._categories[dm.AnnotationType.points] = dst_point_cat + + def _make_label_id_map(self, src_label_cat, dst_label_cat): + id_mapping = { + src_id: dst_label_cat.find(src_label_cat[src_id].name, src_label_cat[src_id].parent)[0] + for src_id in range(len(src_label_cat or ())) + } + self._map_id = lambda src_id: id_mapping.get(src_id, None) + + def categories(self): + return self._categories + + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if getattr(ann, "label", None) is not None: + conv_label = self._map_id(ann.label) + if conv_label is None: + continue + + ann = ann.wrap(label=conv_label) + else: + ann = ann.wrap() + + if ann and isinstance(ann, dm.Skeleton): + ann = ann.wrap(elements=[e.wrap(label=self._map_id(e.label)) for e in ann.elements]) + + annotations.append(ann) + + return item.wrap(annotations=annotations) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py index bf467e09aa..53cbaeb940 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py @@ -19,7 +19,7 @@ TaskStatus, TaskType, ) -from src.crons.state_trackers import retrieve_annotations +from src.crons.state_trackers import track_completed_escrows from src.db import SessionLocal from src.models.cvat import Assignment, Image, Job, Project, Task, User from src.models.webhook import Webhook @@ -132,7 +132,7 @@ def test_retrieve_annotations(self): mock_cloud_client.make_client = Mock(return_value=mock_storage_client) mock_cloud_client.S3Client = Mock(return_value=mock_storage_client) - retrieve_annotations() + track_completed_escrows() webhook = ( self.session.query(Webhook) @@ -180,7 +180,7 @@ def test_retrieve_annotations_unfinished_jobs(self): self.session.add(cvat_job) self.session.commit() - retrieve_annotations() + track_completed_escrows() self.session.commit() db_project = self.session.query(Project).filter_by(id=project_id).first() @@ -259,7 +259,7 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): mock_S3Client.return_value.create_file = mock_create_file mock_annotations.side_effect = Exception("Connection error") - retrieve_annotations() + track_completed_escrows() webhook = ( self.session.query(Webhook) @@ -338,7 +338,7 @@ def test_retrieve_annotations_error_uploading_files(self): manifest = json.load(data) mock_get_manifest.return_value = manifest - retrieve_annotations() + track_completed_escrows() webhook = ( self.session.query(Webhook) From 8722d534fc4676e5335ec4b75b5ecb2ed506f5ef Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 7 Feb 2024 13:57:03 +0200 Subject: [PATCH 30/82] Fix extra gt boxes in merged results from excor --- .../src/handlers/job_export.py | 165 +++++++++++++----- 1 file changed, 121 insertions(+), 44 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 65048552eb..b717f9f8a7 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -132,6 +132,10 @@ def _export_dataset(self, dataset: dm.Dataset, output_dir: str): def _process_dataset( self, dataset: dm.Dataset, *, ann_descriptor: FileDescriptor ) -> dm.Dataset: + dataset = dataset.transform( + "map_subsets", mapping=[(sn, dm.DEFAULT_SUBSET_NAME) for sn in dataset.subsets()] + ) + # TODO: remove complete duplicates in annotations if self._is_merged_dataset(ann_descriptor): @@ -382,45 +386,38 @@ def _init_merged_dataset(self): self.bbox_label_to_merged = bbox_label_to_merged for bbox_sample in self.boxes_dataset: - self.merged_dataset.put( - bbox_sample.wrap( - annotations=[ - dm.Skeleton( - elements=[ - dm.Points( - [0, 0], - visibility=[dm.Points.Visibility.hidden], - label=label_cat.find( - point_label, - parent=label_cat[bbox_label_to_merged[bbox.label]].name, - )[0], - ) - for point_label in points_cat[ - bbox_label_to_merged[bbox.label] - ].labels - ], - label=bbox_label_to_merged[bbox.label], - id=bbox.id, - ) - for bbox in bbox_sample.annotations - if isinstance(bbox, dm.Bbox) - ] - ) - ) + self.merged_dataset.put(bbox_sample.wrap(annotations=[])) def _parse_dataset(self, ann_descriptor: FileDescriptor, dataset_dir: str) -> Dataset: annotation_utils.prepare_cvat_annotations_for_dm(dataset_dir) return super()._parse_dataset(ann_descriptor, dataset_dir) def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) -> Dataset: + dataset = super()._process_dataset(dataset, ann_descriptor=ann_descriptor) + + job_label_cat = dataset.categories()[dm.AnnotationType.label] + merged_skeleton_id = None + merged_skeleton_name = None + + merged_label_cat = self.merged_dataset.categories()[dm.AnnotationType.label] + merged_points_cat = self.merged_dataset.categories()[dm.AnnotationType.points] + # We need to convert point arrays, which cannot be represented in COCO directly, - # into the 1-point skeletons, compatible with COCO person keypoints, which is the + # into the skeletons, compatible with COCO person keypoints, which is the # required output format + converted_job_dataset = annotation_utils.convert_point_arrays_dataset_to_1_point_skeletons( + dataset, labels=[label.name for label in job_label_cat] + ) - job_label_cat = dataset.categories()[dm.AnnotationType.label] + # Accumulate points into the merged dataset + add boxes + for job_sample in dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] - for point_sample in dataset: - roi_info = self.roi_name_to_roi_info[os.path.basename(point_sample.id)] + if merged_skeleton_name is None: + merged_skeleton_id = self.bbox_label_to_merged[roi_info.bbox_label] + merged_skeleton_name = self.merged_dataset.categories()[dm.AnnotationType.label][ + merged_skeleton_id + ].name bbox_sample = self.original_key_to_sample[roi_info.original_image_key] assert bbox_sample @@ -439,40 +436,120 @@ def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) offset_x = old_bbox.x - roi_info.bbox_x offset_y = old_bbox.y - roi_info.bbox_y - skeleton = next( - ann - for ann in skeleton_sample.annotations - if isinstance(ann, dm.Skeleton) - if ann.id == roi_info.bbox_id and isinstance(ann, dm.Skeleton) + merged_skeleton = next( + ( + ann + for ann in skeleton_sample.annotations + if isinstance(ann, dm.Skeleton) + if ann.id == roi_info.bbox_id and isinstance(ann, dm.Skeleton) + ), + None, ) + if not merged_skeleton: + merged_skeleton = dm.Skeleton( + elements=[ + dm.Points( + [0, 0], + visibility=[dm.Points.Visibility.hidden], + label=merged_label_cat.find( + point_label, + parent=merged_label_cat[ + self.bbox_label_to_merged[old_bbox.label] + ].name, + )[0], + ) + for point_label in merged_points_cat[ + self.bbox_label_to_merged[old_bbox.label] + ].labels + ], + label=self.bbox_label_to_merged[old_bbox.label], + id=old_bbox.id, + ) + skeleton_sample.annotations.append(merged_skeleton) # TODO: think about discarding invalid annotations (points) # For now, just take the first one available, as only 1 must be put by annotators - point_sample_points = annotation_utils.flatten_points( - [p for p in point_sample.annotations if isinstance(p, dm.Points)] + job_sample_points = annotation_utils.flatten_points( + [p for p in job_sample.annotations if isinstance(p, dm.Points)] ) - if not point_sample_points: + if not job_sample_points: continue - annotated_point = point_sample_points[0] + job_point = job_sample_points[0] skeleton_point_idx, skeleton_point = next( (p_idx, p) - for p_idx, p in enumerate(skeleton.elements) + for p_idx, p in enumerate(merged_skeleton.elements) if p.label == self.job_point_label_id_to_merged_label_id[ - (roi_info.bbox_label, job_label_cat[annotated_point.label].name) + (roi_info.bbox_label, job_label_cat[job_point.label].name) ] ) - skeleton_point.points[:2] = annotated_point.points[:2] - skeleton_point.visibility[0] = annotated_point.visibility[0] + skeleton_point = skeleton_point.wrap( + points=job_point.points[:2], visibility=[job_point.visibility[0]] + ) - skeleton.elements[skeleton_point_idx] = annotation_utils.shift_ann( + skeleton_point = annotation_utils.shift_ann( skeleton_point, offset_x=offset_x, offset_y=offset_y, img_h=image_h, img_w=image_w ) - return super()._process_dataset(dataset, ann_descriptor=ann_descriptor) + merged_skeleton.elements[skeleton_point_idx] = skeleton_point + + # Append skeleton bbox + converted_sample = converted_job_dataset.get(job_sample.id, subset=job_sample.subset) + assert converted_sample + + skeleton_bbox = annotation_utils.shift_ann( + old_bbox, + offset_x=-offset_x, + offset_y=-offset_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + # Join annotations into a group for correct distance comparison + skeleton_group = 1 + converted_skeleton = next( + s for s in converted_sample.annotations if isinstance(s, dm.Skeleton) + ) + converted_skeleton.group = skeleton_group + skeleton_bbox.group = skeleton_group + skeleton_bbox.label = converted_skeleton.label + converted_job_dataset.put( + converted_sample.wrap(annotations=converted_sample.annotations + [skeleton_bbox]) + ) + + # Rename the job skeleton and point to the original names + assert merged_skeleton_name + converted_label_cat: dm.LabelCategories = converted_job_dataset.categories()[ + dm.AnnotationType.label + ] + converted_points_cat: dm.PointsCategories = converted_job_dataset.categories()[ + dm.AnnotationType.points + ] + + assert len(converted_label_cat) == 2 + converted_skeleton_id, converted_skeleton_label = next( + (i, c) for i, c in enumerate(converted_label_cat) if not c.parent + ) + converted_point_label = next(c for c in converted_label_cat if c.parent) + + converted_skeleton_label.name = merged_skeleton_name + converted_point_label.parent = merged_skeleton_name + + merged_point_label_id = self.job_point_label_id_to_merged_label_id[ + (merged_skeleton_id, converted_point_label.name) + ] + converted_point_label.name = self.merged_dataset.categories()[dm.AnnotationType.label][ + merged_point_label_id + ].name + + converted_points_cat[converted_skeleton_id].labels = [converted_point_label.name] + + converted_label_cat._reindex() + + return converted_job_dataset def _process_merged_dataset(self, merged_dataset: Dataset) -> Dataset: return merged_dataset From 5771d227ca12bca9e1218a80edc9afe1d5172996 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 7 Feb 2024 19:35:52 +0200 Subject: [PATCH 31/82] Implement skeleton matching --- .../src/handlers/job_export.py | 1 + .../src/core/tasks/skeletons_from_boxes.py | 126 ++++++++ .../cvat/recording-oracle/src/core/types.py | 1 + .../handlers/process_intermediate_results.py | 276 +++++++++++++++++- .../src/validation/annotation_matching.py | 2 + .../src/validation/dataset_comparison.py | 237 ++++++++++++++- 6 files changed, 630 insertions(+), 13 deletions(-) create mode 100644 packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index b717f9f8a7..fb9b0df7c8 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -468,6 +468,7 @@ def _process_dataset(self, dataset: Dataset, *, ann_descriptor: FileDescriptor) skeleton_sample.annotations.append(merged_skeleton) # TODO: think about discarding invalid annotations (points) + # e.g. everything beyond the bbox, duplicates, and extra points. # For now, just take the first one available, as only 1 must be put by annotators job_sample_points = annotation_utils.flatten_points( [p for p in job_sample.annotations if isinstance(p, dm.Points)] diff --git a/packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py new file mode 100644 index 0000000000..b6b680c19a --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/tasks/skeletons_from_boxes.py @@ -0,0 +1,126 @@ +import os +from pathlib import Path +from tempfile import TemporaryDirectory +from typing import Dict, Sequence, Tuple + +import attrs +import datumaro as dm +from attrs import frozen +from datumaro.util import dump_json, parse_json + +SkeletonBboxMapping = Dict[int, int] + + +# TODO: migrate to pydantic +@frozen(kw_only=True) +class RoiInfo: + original_image_key: int + bbox_id: int + bbox_x: int + bbox_y: int + bbox_label: int + + # RoI is centered on the bbox center + # Coordinates can be out of image boundaries. + # In this case RoI includes extra margins to be centered on bbox center + roi_x: int + roi_y: int + roi_w: int + roi_h: int + + def asdict(self) -> dict: + return attrs.asdict(self, recurse=False) + + +RoiInfos = Sequence[RoiInfo] + +RoiFilenames = Dict[int, str] + +PointLabelsMapping = Dict[Tuple[str, str], str] +"(skeleton, point) -> job point name" + + +class TaskMetaLayout: + GT_FILENAME = "gt.json" + BOXES_FILENAME = "boxes.json" + POINT_LABELS_FILENAME = "point_labels.json" + SKELETON_BBOX_MAPPING_FILENAME = "skeleton_bbox_mapping.json" + ROI_INFO_FILENAME = "rois.json" + + ROI_FILENAMES_FILENAME = "roi_filenames.json" + # this is separated from the general roi info to make name mangling more "optional" + + +class TaskMetaSerializer: + GT_DATASET_FORMAT = "coco_person_keypoints" + BBOX_DATASET_FORMAT = "coco_instances" + + def serialize_gt_annotations(self, gt_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + gt_dataset_dir = os.path.join(temp_dir, "gt_dataset") + gt_dataset.export(gt_dataset_dir, self.GT_DATASET_FORMAT) + return ( + Path(gt_dataset_dir) / "annotations" / "person_keypoints_default.json" + ).read_bytes() + + def serialize_bbox_annotations(self, bbox_dataset: dm.Dataset) -> bytes: + with TemporaryDirectory() as temp_dir: + bbox_dataset_dir = os.path.join(temp_dir, "bbox_dataset") + bbox_dataset.export(bbox_dataset_dir, self.BBOX_DATASET_FORMAT) + return (Path(bbox_dataset_dir) / "annotations" / "instances_default.json").read_bytes() + + def serialize_skeleton_bbox_mapping(self, skeleton_bbox_mapping: SkeletonBboxMapping) -> bytes: + return dump_json({str(k): str(v) for k, v in skeleton_bbox_mapping.items()}) + + def serialize_roi_info(self, rois_info: RoiInfos) -> bytes: + return dump_json([roi_info.asdict() for roi_info in rois_info]) + + def serialize_roi_filenames(self, roi_filenames: RoiFilenames) -> bytes: + return dump_json({str(k): v for k, v in roi_filenames.items()}) + + def serialize_point_labels(self, point_labels: PointLabelsMapping) -> bytes: + return dump_json( + [ + { + "skeleton_label": k[0], + "point_label": k[1], + "job_point_label": v, + } + for k, v in point_labels.items() + ] + ) + + def parse_gt_annotations(self, gt_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(gt_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.GT_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_bbox_annotations(self, bbox_dataset_data: bytes) -> dm.Dataset: + with TemporaryDirectory() as temp_dir: + annotations_filename = os.path.join(temp_dir, "annotations.json") + with open(annotations_filename, "wb") as f: + f.write(bbox_dataset_data) + + dataset = dm.Dataset.import_from(annotations_filename, format=self.BBOX_DATASET_FORMAT) + dataset.init_cache() + return dataset + + def parse_skeleton_bbox_mapping(self, skeleton_bbox_mapping_data: bytes) -> SkeletonBboxMapping: + return {int(k): int(v) for k, v in parse_json(skeleton_bbox_mapping_data).items()} + + def parse_roi_info(self, rois_info_data: bytes) -> RoiInfos: + return [RoiInfo(**roi_info) for roi_info in parse_json(rois_info_data)] + + def parse_roi_filenames(self, roi_filenames_data: bytes) -> RoiFilenames: + return {int(k): v for k, v in parse_json(roi_filenames_data).items()} + + def parse_point_labels(self, point_labels_data: bytes) -> PointLabelsMapping: + return { + (v["skeleton_label"], v["point_label"]): v["job_point_label"] + for v in parse_json(point_labels_data) + } diff --git a/packages/examples/cvat/recording-oracle/src/core/types.py b/packages/examples/cvat/recording-oracle/src/core/types.py index ab9f3492e3..e98c0383b0 100644 --- a/packages/examples/cvat/recording-oracle/src/core/types.py +++ b/packages/examples/cvat/recording-oracle/src/core/types.py @@ -15,6 +15,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" image_boxes_from_points = "IMAGE_BOXES_FROM_POINTS" + image_skeletons_from_boxes = "IMAGE_SKELETONS_FROM_BOXES" class OracleWebhookTypes(str, Enum): diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 5d786b0a28..98ca2c4f14 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -11,6 +11,7 @@ from sqlalchemy.orm import Session import src.core.tasks.boxes_from_points as boxes_from_points_task +import src.core.tasks.skeletons_from_boxes as skeletons_from_boxes_task import src.services.validation as db_service from src.core.annotation_meta import AnnotationMeta from src.core.config import Config @@ -26,6 +27,7 @@ BboxDatasetComparator, DatasetComparator, PointsDatasetComparator, + SkeletonDatasetComparator, ) @@ -46,6 +48,7 @@ class ValidationFailure: TaskType.image_points: "coco_person_keypoints", TaskType.image_boxes: "coco_instances", TaskType.image_boxes_from_points: "coco_instances", + TaskType.image_skeletons_from_boxes: "coco_person_keypoints", } DM_GT_DATASET_FORMAT_MAPPING = { @@ -53,6 +56,7 @@ class ValidationFailure: TaskType.image_points: "coco_instances", # we compare points against boxes TaskType.image_boxes: "coco_instances", TaskType.image_boxes_from_points: "coco_instances", + TaskType.image_skeletons_from_boxes: "coco_person_keypoints", } @@ -61,6 +65,7 @@ class ValidationFailure: TaskType.image_boxes: BboxDatasetComparator, TaskType.image_points: PointsDatasetComparator, TaskType.image_boxes_from_points: BboxDatasetComparator, + TaskType.image_skeletons_from_boxes: SkeletonDatasetComparator, } _JobResults = Dict[int, float] @@ -76,10 +81,10 @@ def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): self.input_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] self.gt_annotations: Optional[io.IOBase] = None - self.job_annotations: Optional[io.IOBase] = None + self.job_annotations: Optional[Dict[int, io.IOBase]] = None self.merged_annotations: Optional[io.IOBase] = None - def validate(self) -> Tuple[_JobResults, _RejectedJobs]: + def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: assert self.gt_annotations is not None assert self.job_annotations is not None assert self.merged_annotations is not None @@ -339,6 +344,269 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: return job_results, rejected_job_ids, updated_merged_dataset_archive +class _SkeletonsFromBoxesValidator(_TaskValidator): + def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): + super().__init__(escrow_address, chain_id, manifest) + + ( + roi_filenames, + roi_infos, + boxes_dataset, + job_label_mapping, + gt_dataset, + skeletons_to_boxes_mapping, + ) = self._download_task_meta() + + self.boxes_dataset = boxes_dataset + self.original_key_to_sample = {sample.attributes["id"]: sample for sample in boxes_dataset} + + self.job_label_mapping = job_label_mapping + + self.gt_dataset = gt_dataset + + self.bbox_key_to_sample = { + bbox.id: sample + for sample in boxes_dataset + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + } + + self.skeleton_key_to_sample = { + skeleton.id: sample + for sample in gt_dataset + for skeleton in sample.annotations + if isinstance(skeleton, dm.Skeleton) + } + + self.bbox_key_to_skeleton_key = {v: k for k, v in skeletons_to_boxes_mapping.items()} + self.roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in roi_infos} + self.roi_name_to_roi_info: Dict[str, skeletons_from_boxes_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self.roi_info_by_id[roi_id] + for roi_id, roi_filename in roi_filenames.items() + } + + self.bbox_offset_by_roi_id = {} + "Offset from old to new coords, (dx, dy)" + + for roi_info in roi_infos: + bbox_sample = self.bbox_key_to_sample[roi_info.bbox_id] + + old_bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + offset_x = roi_info.bbox_x - old_bbox.x + offset_y = roi_info.bbox_y - old_bbox.y + + self.bbox_offset_by_roi_id[roi_info.bbox_id] = (offset_x, offset_y) + + def _download_task_meta(self): + layout = skeletons_from_boxes_task.TaskMetaLayout() + serializer = skeletons_from_boxes_task.TaskMetaSerializer() + + oracle_data_bucket = BucketAccessInfo.from_raw_url( + Config.exchange_oracle_storage_config.bucket_url() + ) + # TODO: add + # credentials=BucketCredentials() + "Exchange Oracle's private bucket info" + + storage_client = make_cloud_client(oracle_data_bucket) + + roi_filenames = serializer.parse_roi_filenames( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME + ), + ) + ) + + rois = serializer.parse_roi_info( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME + ), + ) + ) + + boxes_dataset = serializer.parse_bbox_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.BOXES_FILENAME + ), + ) + ) + + job_label_mapping = serializer.parse_point_labels( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.POINT_LABELS_FILENAME + ), + ) + ) + + gt_dataset = serializer.parse_gt_annotations( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.GT_FILENAME + ), + ) + ) + + skeletons_to_boxes_mapping = serializer.parse_skeleton_bbox_mapping( + storage_client.download_file( + oracle_data_bucket.url.bucket_name, + compose_data_bucket_filename( + self.escrow_address, self.chain_id, layout.SKELETON_BBOX_MAPPING_FILENAME + ), + ) + ) + + return ( + roi_filenames, + rois, + boxes_dataset, + job_label_mapping, + gt_dataset, + skeletons_to_boxes_mapping, + ) + + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: + job_label_cat: dm.LabelCategories = job_dataset.categories()[dm.AnnotationType.label] + assert len(job_label_cat) == 2 + job_skeleton_label_id, job_skeleton_label = next( + (i, c) for i, c in enumerate(job_label_cat) if not c.parent + ) + job_point_label_id, job_point_label = next( + (i, c) for i, c in enumerate(job_label_cat) if c.parent + ) + + gt_label_cat = self.gt_dataset.categories()[dm.AnnotationType.label] + gt_point_label_id = gt_label_cat.find(job_point_label.name, parent=job_skeleton_label.name)[ + 0 + ] + + job_gt_dataset = dm.Dataset(categories=job_dataset.categories(), media_type=dm.Image) + for job_sample in job_dataset: + roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + + gt_skeleton_key = self.bbox_key_to_skeleton_key.get(roi_info.bbox_id, None) + if gt_skeleton_key is None: + continue # roi is not from GT set + + bbox_sample = self.bbox_key_to_sample[roi_info.bbox_id] + bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + + gt_sample = self.skeleton_key_to_sample[gt_skeleton_key] + gt_skeleton = next( + skeleton + for skeleton in gt_sample.annotations + if skeleton.id == gt_skeleton_key + if isinstance(skeleton, dm.Skeleton) + ) + + roi_shift_x, roi_shift_y = self.bbox_offset_by_roi_id[roi_info.bbox_id] + converted_gt_skeleton = shift_ann( + gt_skeleton, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + converted_bbox = shift_ann( + bbox, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + # Join annotations into a group for correct distance comparison + skeleton_group = 1 + converted_bbox.group = skeleton_group + converted_gt_skeleton.group = skeleton_group + + # Convert labels + converted_gt_skeleton.label = job_skeleton_label_id + converted_gt_skeleton.elements = [ + p.wrap(label=job_point_label_id) + for p in converted_gt_skeleton.elements + if p.label == gt_point_label_id + ] + + job_gt_dataset.put(job_sample.wrap(annotations=[converted_gt_skeleton, converted_bbox])) + + return job_gt_dataset + + def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: + assert self.job_annotations is not None + assert self.merged_annotations is not None + + manifest = self.manifest + task_type = manifest.annotation.type + dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + + job_annotations = self.job_annotations + merged_annotations = self.merged_annotations + + job_results: Dict[int, float] = {} + rejected_job_ids: List[int] = [] + + with TemporaryDirectory() as tempdir: + tempdir = Path(tempdir) + + comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( + min_similarity_threshold=manifest.validation.min_quality + ) + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), format=dataset_format + ) + job_gt_dataset = self._make_gt_dataset_for_job(job_cvat_id, job_dataset) + + job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) + job_results[job_cvat_id] = job_mean_accuracy + + if job_mean_accuracy < manifest.validation.min_quality: + rejected_job_ids.append(job_cvat_id) + + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + extract_zip_archive(merged_annotations, merged_dataset_path) + + merged_dataset = dm.Dataset.import_from( + os.fspath(merged_dataset_path), format=merged_dataset_format + ) + put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) + + updated_merged_dataset_path = tempdir / "merged_updated" + merged_dataset.export( + updated_merged_dataset_path, merged_dataset_format, save_media=False + ) + + updated_merged_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) + updated_merged_dataset_archive.seek(0) + + return job_results, rejected_job_ids, updated_merged_dataset_archive + + def process_intermediate_results( session: Session, *, @@ -357,6 +625,8 @@ def process_intermediate_results( validator_type = _TaskValidator elif task_type == TaskType.image_boxes_from_points: validator_type = _BoxesFromPointsValidator + elif task_type == TaskType.image_skeletons_from_boxes: + validator_type = _SkeletonsFromBoxesValidator else: raise Exception(f"Unknown task type {task_type}") @@ -465,6 +735,8 @@ def put_gt_into_merged_dataset( merged_dataset.update(gt_dataset) case TaskType.image_boxes_from_points: merged_dataset.update(gt_dataset) + case TaskType.image_skeletons_from_boxes: + merged_dataset.update(gt_dataset) # TODO: map labels case _: assert False, f"Unknown task type {manifest.annotation.type}" diff --git a/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py b/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py index d5ab648c15..70d4817cd6 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py +++ b/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py @@ -103,6 +103,8 @@ def match_annotations( for a, _ in itertools.zip_longest(a_anns, range(max_ann_count), fillvalue=None) ] ) + + distances[~np.isfinite(distances)] = 1 distances[distances > 1 - min_similarity] = 1 if a_anns and b_anns: diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 884478ef56..4ee738c225 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -1,34 +1,43 @@ import itertools from abc import ABCMeta, abstractmethod -from typing import Any, Callable, Dict, Tuple +from typing import Any, Callable, Dict, Optional, Sequence, Tuple import datumaro as dm import numpy as np from attrs import define -from .annotation_matching import Bbox, Point, bbox_iou, match_annotations, point_to_bbox_cmp +from .annotation_matching import ( + Bbox, + MatchResult, + Point, + bbox_iou, + match_annotations, + point_to_bbox_cmp, +) class CachedSimilarityFunction: + def __init__( + self, sim_fn: Callable, *, cache: Optional[Dict[Tuple[int, int], float]] = None + ) -> None: + self.cache: Dict[Tuple[int, int], float] = cache or {} + self.sim_fn = sim_fn + def __call__(self, gt_ann: Any, ds_ann: Any) -> float: key = ( id(gt_ann), id(ds_ann), - ) # make sure the boxes have stable ids before calling this! - cached_value = self._memo.get(key) + ) # make sure the annotations have stable ids before calling this + cached_value = self.cache.get(key) if cached_value is None: - cached_value = self._inner(gt_ann, ds_ann) - self._memo[key] = cached_value + cached_value = self.sim_fn(gt_ann, ds_ann) + self.cache[key] = cached_value return cached_value def clear_cache(self): - self._memo.clear() - - def __init__(self, inner: Callable) -> None: - self._memo: Dict[Tuple[int, int], float] = {} - self._inner = inner + self.cache.clear() @define @@ -126,3 +135,209 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: all_similarities.append(sim) return np.mean(all_similarities) if all_similarities else 0 + + +class SkeletonDatasetComparator(DatasetComparator): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._skeleton_info: Dict[int, list[str]] = {} + self.oks_sigma = 0.1 # TODO: try to estimate from GT + self.categories: Optional[dm.CategoriesInfo] = None + + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: + self.categories = gt_dataset.categories() + + all_similarities = [] + total_anns_to_compare = 0 + + for ds_sample in ds_dataset: + gt_sample = gt_dataset.get(ds_sample.id) + + if not gt_sample: + continue + + matching_result, similarity_fn = self.match_skeletons(gt_sample, ds_sample) + + for gt_skeleton, ds_skeleton in itertools.chain( + matching_result.matches, + matching_result.mispred, + zip(matching_result.a_extra, itertools.repeat(None)), + zip(itertools.repeat(None), matching_result.b_extra), + ): + sim = similarity_fn(gt_skeleton, ds_skeleton) if gt_skeleton and ds_skeleton else 0 + all_similarities.append(sim) + + total_anns_to_compare += (gt_skeleton is not None) + (ds_skeleton is not None) + + accuracy = 0 + if total_anns_to_compare: + accuracy = 2 * np.sum(all_similarities) / total_anns_to_compare + + return accuracy + + def _get_skeleton_info(self, skeleton_label_id: int): + label_cat: dm.LabelCategories = self.categories[dm.AnnotationType.label] + skeleton_info = self._skeleton_info.get(skeleton_label_id) + + if skeleton_info is None: + skeleton_label_name = label_cat[skeleton_label_id].name + + # Build a sorted list of sublabels to arrange skeleton points during comparison + skeleton_info = sorted( + idx for idx, label in enumerate(label_cat) if label.parent == skeleton_label_name + ) + self._skeleton_info[skeleton_label_id] = skeleton_info + + return skeleton_info + + def match_skeletons(self, item_a, item_b): + a_skeletons = [a for a in item_a.annotations if isinstance(a, dm.Skeleton)] + b_skeletons = [a for a in item_b.annotations if isinstance(a, dm.Skeleton)] + + # Convert skeletons to point lists for comparison + # This is required to compute correct per-instance distance + # It is assumed that labels are the same in the datasets + skeleton_infos = {} + points_map = {} + skeleton_map = {} + a_points = [] + b_points = [] + for source, source_points in [(a_skeletons, a_points), (b_skeletons, b_points)]: + for skeleton in source: + skeleton_info = skeleton_infos.setdefault( + skeleton.label, self._get_skeleton_info(skeleton.label) + ) + + # Merge skeleton points into a single list + # The list is ordered by skeleton_info + skeleton_points = [ + next((p for p in skeleton.elements if p.label == sublabel), None) + for sublabel in skeleton_info + ] + + # Build a single Points object for further comparisons + merged_points = dm.Points() + merged_points.points = np.ravel( + [p.points if p else [0, 0] for p in skeleton_points] + ) + merged_points.visibility = np.ravel( + [p.visibility if p else [dm.Points.Visibility.absent] for p in skeleton_points] + ) + merged_points.label = skeleton.label + # no per-point attributes currently in CVAT + + points_map[id(merged_points)] = skeleton + skeleton_map[id(skeleton)] = merged_points + source_points.append(merged_points) + + instance_map = {} + for source in [item_a.annotations, item_b.annotations]: + for instance_group in dm.ops.find_instances(source): + instance_bbox = self._instance_bbox(instance_group) + + instance_group = [ + skeleton_map[id(a)] if isinstance(a, dm.Skeleton) else a + for a in instance_group + if not isinstance(a, dm.Skeleton) or skeleton_map[id(a)] is not None + ] + for ann in instance_group: + instance_map[id(ann)] = [instance_group, instance_bbox] + + keypoints_matcher = self._KeypointsMatcher(instance_map=instance_map, sigma=self.oks_sigma) + keypoints_similarity = CachedSimilarityFunction(keypoints_matcher.distance) + matching_result = match_annotations( + a_points, + b_points, + similarity=keypoints_similarity, + min_similarity=self.min_similarity_threshold, + ) + + distances = keypoints_similarity.cache + + matched, mismatched, a_extra, b_extra = matching_result + + matched = [(points_map[id(p_a)], points_map[id(p_b)]) for (p_a, p_b) in matched] + mismatched = [(points_map[id(p_a)], points_map[id(p_b)]) for (p_a, p_b) in mismatched] + a_extra = [points_map[id(p_a)] for p_a in a_extra] + b_extra = [points_map[id(p_b)] for p_b in b_extra] + + # Map points back to skeletons + for p_a_id, p_b_id in list(distances.keys()): + dist = distances.pop((p_a_id, p_b_id)) + distances[(id(points_map[p_a_id]), id(points_map[p_b_id]))] = dist + + similarity_fn = CachedSimilarityFunction(None) + similarity_fn.cache.update(distances) + + return MatchResult(matched, mismatched, a_extra, b_extra), similarity_fn + + def _instance_bbox( + self, instance_anns: Sequence[dm.Annotation] + ) -> Tuple[float, float, float, float]: + return dm.ops.max_bbox( + a.get_bbox() if isinstance(a, dm.Skeleton) else a + for a in instance_anns + if hasattr(a, "get_bbox") and not a.attributes.get("outside", False) + ) + + @define(kw_only=True) + class _KeypointsMatcher(dm.ops.PointsMatcher): + def distance(self, a: dm.Points, b: dm.Points) -> float: + a_bbox = self.instance_map[id(a)][1] + b_bbox = self.instance_map[id(b)][1] + if dm.ops.bbox_iou(a_bbox, b_bbox) <= 0: + return 0 + + bbox = dm.ops.mean_bbox([a_bbox, b_bbox]) + return self._OKS( + a, + b, + sigma=self.sigma, + bbox=bbox, + visibility_a=[v == dm.Points.Visibility.visible for v in a.visibility], + visibility_b=[v == dm.Points.Visibility.visible for v in b.visibility], + ) + + @classmethod + def _OKS( + cls, a, b, sigma=0.1, bbox=None, scale=None, visibility_a=None, visibility_b=None + ) -> float: + """ + Object Keypoint Similarity metric. + https://cocodataset.org/#keypoints-eval + """ + + p1 = np.array(a.points).reshape((-1, 2)) + p2 = np.array(b.points).reshape((-1, 2)) + if len(p1) != len(p2): + return 0 + + if visibility_a is None: + visibility_a = np.full(len(p1), True) + else: + visibility_a = np.asarray(visibility_a, dtype=bool) + + if visibility_b is None: + visibility_b = np.full(len(p2), True) + else: + visibility_b = np.asarray(visibility_b, dtype=bool) + + if not scale: + if bbox is None: + bbox = dm.ops.mean_bbox([a, b]) + scale = bbox[2] * bbox[3] + + total_vis = np.sum(visibility_a | visibility_b, dtype=float) + if not total_vis: + return 1.0 + + dists = np.linalg.norm(p1 - p2, axis=1) + return ( + np.sum( + visibility_a + * visibility_b + * np.exp(-(dists**2) / (2 * scale * ((2 * sigma) ** 2))) + ) + / total_vis + ) From 2e03d7916070eb627ac75862b141a3fd97c619f2 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 11:16:48 +0200 Subject: [PATCH 32/82] Fix labels in merged annotations --- .../handlers/process_intermediate_results.py | 9 +- .../recording-oracle/src/utils/annotations.py | 167 ++++++++++++++++++ 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 98ca2c4f14..330e84f68f 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -21,7 +21,7 @@ from src.core.validation_meta import JobMeta, ResultMeta, ValidationMeta from src.services.cloud import make_client as make_cloud_client from src.services.cloud.utils import BucketAccessInfo -from src.utils.annotations import shift_ann +from src.utils.annotations import ProjectLabels, shift_ann from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive from src.validation.dataset_comparison import ( BboxDatasetComparator, @@ -736,7 +736,12 @@ def put_gt_into_merged_dataset( case TaskType.image_boxes_from_points: merged_dataset.update(gt_dataset) case TaskType.image_skeletons_from_boxes: - merged_dataset.update(gt_dataset) # TODO: map labels + # The original behavior is broken for skeletons + gt_dataset = dm.Dataset(gt_dataset) + gt_dataset = gt_dataset.transform( + ProjectLabels, dst_labels=merged_dataset.categories()[dm.AnnotationType.label] + ) + merged_dataset.update(gt_dataset) case _: assert False, f"Unknown task type {manifest.annotation.type}" diff --git a/packages/examples/cvat/recording-oracle/src/utils/annotations.py b/packages/examples/cvat/recording-oracle/src/utils/annotations.py index 5a62b80fb4..75ec29ec15 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/recording-oracle/src/utils/annotations.py @@ -1,5 +1,9 @@ +from copy import deepcopy +from typing import Iterable, Optional, Tuple, Union + import datumaro as dm import numpy as np +from datumaro.util import mask_tools def shift_ann( @@ -37,3 +41,166 @@ def shift_ann( assert False, f"Unsupported annotation type '{ann.type}'" return shifted_ann + + +class ProjectLabels(dm.ItemTransform): + """ + Changes the order of labels in the dataset from the existing + to the desired one, removes unknown labels and adds new labels. + Updates or removes the corresponding annotations.|n + |n + Labels are matched by names (case dependent). Parent labels are only kept + if they are present in the resulting set of labels. If new labels are + added, and the dataset has mask colors defined, new labels will obtain + generated colors.|n + |n + Useful for merging similar datasets, whose labels need to be aligned.|n + |n + Examples:|n + |s|s- Align the source dataset labels to [person, cat, dog]:|n + + |s|s.. code-block:: + + |s|s|s|s%(prog)s -l person -l cat -l dog + """ + + @classmethod + def build_cmdline_parser(cls, **kwargs): + parser = super().build_cmdline_parser(**kwargs) + parser.add_argument( + "-l", + "--label", + action="append", + dest="dst_labels", + help="Label name (repeatable, ordered)", + ) + return parser + + def __init__( + self, + extractor: dm.IExtractor, + dst_labels: Union[Iterable[Union[str, Tuple[str, str]]], dm.LabelCategories], + ): + super().__init__(extractor) + + self._categories = {} + + src_categories = self._extractor.categories() + + src_label_cat: Optional[dm.LabelCategories] = src_categories.get(dm.AnnotationType.label) + src_point_cat: Optional[dm.PointsCategories] = src_categories.get(dm.AnnotationType.points) + + if isinstance(dst_labels, dm.LabelCategories): + dst_label_cat = deepcopy(dst_labels) + else: + dst_labels = list(dst_labels) + + if src_label_cat: + dst_label_cat = dm.LabelCategories(attributes=deepcopy(src_label_cat.attributes)) + + for dst_label in dst_labels: + assert isinstance(dst_label, str) or isinstance(dst_label, tuple) + + dst_parent = "" + if isinstance(dst_label, tuple): + dst_label, dst_parent = dst_label + + src_label = src_label_cat.find(dst_label, dst_parent)[1] + if src_label is not None: + dst_label_cat.add( + dst_label, src_label.parent, deepcopy(src_label.attributes) + ) + else: + dst_label_cat.add(dst_label, dst_parent) + else: + dst_label_cat = dm.LabelCategories.from_iterable(dst_labels) + + for label in dst_label_cat: + if label.parent not in dst_label_cat: + label.parent = "" + + if src_point_cat: + # Copy nested labels + for skeleton_label_id, skeleton_spec in src_point_cat.items.items(): + skeleton_label = src_label_cat[skeleton_label_id] + for child_label_name in skeleton_spec.labels: + if ( + skeleton_label.name in dst_label_cat + and not dst_label_cat.find(child_label_name, parent=skeleton_label.name)[1] + ): + dst_label_cat.add( + child_label_name, + parent=skeleton_label.name, + attributes=skeleton_label.attributes, + ) + + self._categories[dm.AnnotationType.label] = dst_label_cat + + self._make_label_id_map(src_label_cat, dst_label_cat) + + src_mask_cat = src_categories.get(dm.AnnotationType.mask) + if src_mask_cat is not None: + assert src_label_cat is not None + dst_mask_cat = dm.MaskCategories(attributes=deepcopy(src_mask_cat.attributes)) + for old_id, old_color in src_mask_cat.colormap.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_mask_cat: + dst_mask_cat.colormap[new_id] = deepcopy(old_color) + + # Generate new colors for new labels, keep old untouched + existing_colors = set(dst_mask_cat.colormap.values()) + color_bank = iter( + mask_tools.generate_colormap(len(dst_label_cat), include_background=False).values() + ) + for new_id, new_label in enumerate(dst_label_cat): + if new_label.name in src_label_cat: + continue + if new_id in dst_mask_cat: + continue + + color = next(color_bank) + while color in existing_colors: + color = next(color_bank) + + dst_mask_cat.colormap[new_id] = color + + self._categories[dm.AnnotationType.mask] = dst_mask_cat + + if src_point_cat is not None: + assert src_label_cat is not None + dst_point_cat = dm.PointsCategories(attributes=deepcopy(src_point_cat.attributes)) + for old_id, old_cat in src_point_cat.items.items(): + new_id = self._map_id(old_id) + if new_id is not None and new_id not in dst_point_cat: + dst_point_cat.items[new_id] = deepcopy(old_cat) + + self._categories[dm.AnnotationType.points] = dst_point_cat + + def _make_label_id_map(self, src_label_cat, dst_label_cat): + id_mapping = { + src_id: dst_label_cat.find(src_label_cat[src_id].name, src_label_cat[src_id].parent)[0] + for src_id in range(len(src_label_cat or ())) + } + self._map_id = lambda src_id: id_mapping.get(src_id, None) + + def categories(self): + return self._categories + + def transform_item(self, item): + annotations = [] + for ann in item.annotations: + if getattr(ann, "label", None) is not None: + conv_label = self._map_id(ann.label) + if conv_label is None: + continue + + ann = ann.wrap(label=conv_label) + else: + ann = ann.wrap() + + if ann and isinstance(ann, dm.Skeleton): + ann = ann.wrap(elements=[e.wrap(label=self._map_id(e.label)) for e in ann.elements]) + + annotations.append(ann) + + return item.wrap(annotations=annotations) From 4d20ef763253662e94415523df3e93c19c5f6f3e Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 12:36:09 +0200 Subject: [PATCH 33/82] Update project events handling in excor --- .../cvat/exchange-oracle/src/chain/escrow.py | 1 + .../cvat/exchange-oracle/src/core/config.py | 18 ++- .../process_recording_oracle_webhooks.py | 104 +++++++++++------- .../src/handlers/completed_escrows.py | 4 +- .../cvat/exchange-oracle/src/services/cvat.py | 31 ++++++ 5 files changed, 113 insertions(+), 45 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index f81b2103a5..598773c551 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -5,6 +5,7 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient +from src.core.config import LocalhostConfig from src.services.cloud.utils import parse_bucket_url diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index 7996a802fc..81150b27e6 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -53,6 +53,8 @@ class LocalhostConfig: addr = os.environ.get("LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") job_launcher_url = os.environ.get("LOCALHOST_JOB_LAUNCHER_URL") + + recording_oracle_address = os.environ.get("LOCALHOST_RECORDING_ORACLE_ADDRESS") recording_oracle_url = os.environ.get("LOCALHOST_RECORDING_ORACLE_URL") @@ -75,8 +77,20 @@ class CronConfig: track_assignments_int = int(os.environ.get("TRACK_ASSIGNMENTS_INT", 5)) track_assignments_chunk_size = os.environ.get("TRACK_ASSIGNMENTS_CHUNK_SIZE", 10) - track_completed_escrows_int = int(os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60)) - retrieve_annotations_chunk_size = os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5) + track_completed_escrows_int = int( + # backward compatibility + os.environ.get( + "TRACK_COMPLETED_ESCROWS_INT", os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60) + ) + ) + track_completed_escrows_chunk_size = os.environ.get( + # backward compatibility + "TRACK_COMPLETED_ESCROWS_CHUNK_SIZE", + os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5), + ) + + rejected_projects_chunk_size = os.environ.get("REJECTED_PROJECTS_CHUNK_SIZE", 20) + accepted_projects_chunk_size = os.environ.get("ACCEPTED_PROJECTS_CHUNK_SIZE", 20) class CvatConfig: diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py index c1b2351289..cfedb705fe 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py @@ -1,6 +1,7 @@ import logging import httpx +from datumaro.util import take_by from sqlalchemy.orm import Session import src.services.cvat as cvat_db_service @@ -61,10 +62,11 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg match webhook.event_type: case RecordingOracleEventType.task_completed: - project = cvat_db_service.get_project_by_escrow_address( - db_session, webhook.escrow_address, for_update=True + chunk_size = CronConfig.accepted_projects_chunk_size + project_ids = cvat_db_service.get_project_cvat_ids_by_escrow_address( + db_session, webhook.escrow_address ) - if not project: + if not project_ids: logger.error( "Unexpected event {} received for an unknown project, " "ignoring (escrow_address={})".format( @@ -73,54 +75,74 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg ) return - if project.status != ProjectStatuses.validation: - logger.error( - "Unexpected event {} received for a project in the {} status, " - "ignoring (escrow_address={})".format( - webhook.event_type, project.status, webhook.escrow_address - ) + for ids_chunk in take_by(project_ids, chunk_size): + projects_chunk = cvat_db_service.get_projects_by_cvat_ids( + db_session, ids_chunk, for_update=True, limit=chunk_size ) - return - new_status = ProjectStatuses.recorded - logger.info( - "Changing project status to {} (escrow_address={})".format( - new_status, webhook.escrow_address - ) - ) + for project in projects_chunk: + if project.status != ProjectStatuses.validation: + logger.error( + "Unexpected event {} received for a project in the {} status, " + "ignoring (escrow_address={}, project={})".format( + webhook.event_type, + project.status, + webhook.escrow_address, + project.cvat_id, + ) + ) + return + + new_status = ProjectStatuses.recorded + logger.info( + "Changing project status to {} (escrow_address={}, project={})".format( + new_status, webhook.escrow_address, project.cvat_id + ) + ) - cvat_db_service.update_project_status(db_session, project.id, new_status) + cvat_db_service.update_project_status(db_session, project.id, new_status) case RecordingOracleEventType.task_rejected: event = RecordingOracleEvent_TaskRejected.parse_obj(webhook.event_data) - project = cvat_db_service.get_project_by_escrow_address( - db_session, webhook.escrow_address, for_update=True - ) - - if project.status != ProjectStatuses.validation: - logger.error( - "Unexpected event {} received for a project in the {} status, " - "ignoring (escrow_address={})".format( - webhook.event_type, project.status, webhook.escrow_address - ) - ) - return - rejected_jobs = cvat_db_service.get_jobs_by_cvat_id(db_session, event.rejected_job_ids) + rejected_project_cvat_ids = set(j.cvat_project_id for j in rejected_jobs) - tasks_to_update = set() - - for job in rejected_jobs: - tasks_to_update.add(job.task.id) - cvat_db_service.update_job_status(db_session, job.id, JobStatuses.new) - - for task_id in tasks_to_update: - cvat_db_service.update_task_status(db_session, task_id, TaskStatus.annotation) + chunk_size = CronConfig.rejected_projects_chunk_size + for chunk_ids in take_by(rejected_project_cvat_ids, chunk_size): + projects_chunk = cvat_db_service.get_projects_by_cvat_ids( + db_session, chunk_ids, for_update=True, limit=chunk_size + ) - cvat_db_service.update_project_status( - db_session, project.id, ProjectStatuses.annotation - ) + for project in projects_chunk: + if project.status != ProjectStatuses.validation: + logger.error( + "Unexpected event {} received for a project in the {} status, " + "ignoring (escrow_address={}, project_id={})".format( + webhook.event_type, + project.status, + webhook.escrow_address, + project.cvat_id, + ) + ) + continue + + rejected_jobs_in_project = [ + j for j in rejected_jobs if j.cvat_project_id == project.cvat_id + ] + tasks_to_update = set() + for job in rejected_jobs_in_project: + tasks_to_update.add(job.task.id) + cvat_db_service.update_job_status(db_session, job.id, JobStatuses.new) + + for task_id in tasks_to_update: + cvat_db_service.update_task_status( + db_session, task_id, TaskStatus.annotation + ) + + cvat_db_service.update_project_status( + db_session, project.id, ProjectStatuses.annotation + ) case _: assert False, f"Unknown recording oracle event {webhook.event_type}" diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py index a5826bea0d..43d0e3a6b5 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py @@ -49,7 +49,7 @@ def _process_plain_escrows(self): session, ProjectStatuses.completed, included_types=plain_task_types, - limit=CronConfig.retrieve_annotations_chunk_size, + limit=CronConfig.track_completed_escrows_chunk_size, for_update=ForUpdateParams(skip_locked=True), ) @@ -195,7 +195,7 @@ def _process_skeletons_from_boxes_escrows(self): session, ProjectStatuses.completed, included_types=[TaskType.image_skeletons_from_boxes], - limit=CronConfig.retrieve_annotations_chunk_size, + limit=CronConfig.track_completed_escrows_chunk_size, ) escrows_with_completed_projects = set() diff --git a/packages/examples/cvat/exchange-oracle/src/services/cvat.py b/packages/examples/cvat/exchange-oracle/src/services/cvat.py index 1d802b8a36..60d2e08664 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cvat.py @@ -1,3 +1,4 @@ +import itertools import uuid from datetime import datetime from typing import List, Optional, Sequence, Union @@ -63,6 +64,27 @@ def get_project_by_id( ) +def get_projects_by_cvat_ids( + session: Session, + project_cvat_ids: Sequence[int], + *, + for_update: Union[bool, ForUpdateParams] = False, + status_in: Optional[List[ProjectStatuses]] = None, + limit: int = 5, +) -> List[Project]: + if status_in: + status_filter_arg = [Project.status.in_(s.value for s in status_in)] + else: + status_filter_arg = [] + + return ( + _maybe_for_update(session.query(Project), enable=for_update) + .where(Project.cvat_id.in_(project_cvat_ids), *status_filter_arg) + .limit(limit) + .all() + ) + + def get_project_by_escrow_address( session: Session, escrow_address: str, *, for_update: Union[bool, ForUpdateParams] = False ) -> Optional[Project]: @@ -90,6 +112,15 @@ def get_projects_by_escrow_address( return projects.all() +def get_project_cvat_ids_by_escrow_address( + session: Session, + escrow_address: str, +) -> List[int]: + projects = session.query(Project).where(Project.escrow_address == escrow_address) + + return list(itertools.chain.from_iterable(projects.values(Project.cvat_id))) + + def get_projects_by_status( session: Session, status: ProjectStatuses, From 54dd6b56b7b54d1e22b77e513f39d261cb89a73f Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 12:50:39 +0200 Subject: [PATCH 34/82] Improve oks sigma comment --- .../recording-oracle/src/validation/dataset_comparison.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 4ee738c225..a1ac3c117b 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -142,9 +142,11 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._skeleton_info: Dict[int, list[str]] = {} - self.oks_sigma = 0.1 # TODO: try to estimate from GT self.categories: Optional[dm.CategoriesInfo] = None + # TODO: find better strategy for sigma estimation + self.oks_sigma = 0.1 # average value for COCO points + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: self.categories = gt_dataset.categories() From 642fc4c9e1ce91c89825f632ffcf8e16ae841bfc Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 18:52:36 +0200 Subject: [PATCH 35/82] Remove local testing assets --- .../cvat/exchange-oracle/src/chain/escrow.py | 5 +- .../annotations/person_keypoints_default.json | 1 - .../annotations/instances_default.json | 329 ------------------ .../annotations/person_keypoints_default.json | 1 - .../manifest_skeletons_from_boxes_local.json | 51 --- 5 files changed, 4 insertions(+), 383 deletions(-) delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json delete mode 100644 packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index 598773c551..4ae41b0caa 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -5,7 +5,7 @@ from human_protocol_sdk.escrow import EscrowData, EscrowUtils from human_protocol_sdk.storage import StorageClient -from src.core.config import LocalhostConfig +from src.core.config import Config from src.services.cloud.utils import parse_bucket_url @@ -67,4 +67,7 @@ def get_job_launcher_address(chain_id: int, escrow_address: str) -> str: def get_recording_oracle_address(chain_id: int, escrow_address: str) -> str: + if address := Config.localhost.recording_oracle_address: + return address + return get_escrow(chain_id, escrow_address).recording_oracle diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json deleted file mode 100644 index 7a3a7fc2cc..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/gt_skeletons/annotations/person_keypoints_default.json +++ /dev/null @@ -1 +0,0 @@ -{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":"","keypoints":["head","r front leg","l front leg","r back leg","l back leg","neck","torso"],"skeleton":[[7,4],[6,2],[6,1],[5,7],[7,6],[6,3]]},{"id":9,"name":"dog","supercategory":"","keypoints":["nose","l ear","r ear"],"skeleton":[[3,1],[2,1]]}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":26598.009200000004,"bbox":[276.84,456.97,112.28,236.89],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[343.77,456.97,2,389.12,683.69,2,326.35,687.92,2,290.69,693.86,2,359.15,685.99,2,342.5,527.96,2,276.84,634.98,2],"num_keypoints":7},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":17348.673300000002,"bbox":[874.63,310.35,139.47,124.39],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[939.77,310.35,2,898.52,434.74,2,994.96,430.32,2,874.63,413.0,2,1014.1,432.35,2,987.86,353.88,2,1007.35,399.89,2],"num_keypoints":7},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":13275.416700000005,"bbox":[377.52,253.64,193.83,68.49],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[377.52,253.64,2,424.44,306.56,2,410.48,303.45,2,519.36,319.3,2,536.8,322.13,2,437.73,263.27,2,571.35,292.16,2],"num_keypoints":7},{"id":4,"image_id":6,"category_id":9,"segmentation":[],"area":26851.028500000004,"bbox":[290.23,467.54,195.95,137.03],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[486.18,604.57,2,459.0,467.54,2,290.23,508.66,2],"num_keypoints":3},{"id":5,"image_id":6,"category_id":9,"segmentation":[],"area":17102.630999999998,"bbox":[681.32,181.06,277.55,61.62],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[958.87,242.68,2,854.96,181.06,2,681.32,241.06,2],"num_keypoints":3}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json deleted file mode 100644 index 0cacc7f0b9..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco 1.0-good/annotations/instances_default.json +++ /dev/null @@ -1,329 +0,0 @@ -{ - "licenses": [{ "name": "", "id": 0, "url": "" }], - "info": { - "contributor": "", - "date_created": "", - "description": "", - "url": "", - "version": "", - "year": "" - }, - "categories": [ - { "id": 1, "name": "cat", "supercategory": "" }, - { "id": 2, "name": "dog", "supercategory": "" } - ], - "images": [ - { - "id": 1, - "width": 1279, - "height": 854, - "file_name": "img1.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 2, - "width": 800, - "height": 1039, - "file_name": "img10.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 3, - "width": 900, - "height": 600, - "file_name": "img11.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 4, - "width": 1280, - "height": 879, - "file_name": "img12.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 5, - "width": 1280, - "height": 1920, - "file_name": "img13.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 6, - "width": 1280, - "height": 1295, - "file_name": "img2.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 7, - "width": 1279, - "height": 853, - "file_name": "img3.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 8, - "width": 1280, - "height": 2278, - "file_name": "img4.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 9, - "width": 1280, - "height": 1039, - "file_name": "img5.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 10, - "width": 1280, - "height": 1920, - "file_name": "img6.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 11, - "width": 1280, - "height": 879, - "file_name": "img7.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 12, - "width": 1280, - "height": 853, - "file_name": "img8.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - }, - { - "id": 13, - "width": 800, - "height": 853, - "file_name": "img9.jpg", - "license": 0, - "flickr_url": "", - "coco_url": "", - "date_captured": 0 - } - ], - "annotations": [ - { - "id": 1, - "image_id": 1, - "category_id": 1, - "segmentation": [], - "area": 37089.83899999999, - "bbox": [336.68, 207.62, 272.9, 135.91], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 2, - "image_id": 1, - "category_id": 1, - "segmentation": [], - "area": 45261.450599999975, - "bbox": [855.72, 252.56, 222.59, 203.34], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 3, - "image_id": 1, - "category_id": 1, - "segmentation": [], - "area": 50793.008, - "bbox": [243.58, 398.11, 164.8, 308.21], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 4, - "image_id": 2, - "category_id": 2, - "segmentation": [], - "area": 347397.2964000001, - "bbox": [176.67, 169.9, 429.66, 808.54], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 5, - "image_id": 3, - "category_id": 1, - "segmentation": [], - "area": 356013.414, - "bbox": [178.2, 104.51, 721.8, 493.23], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 6, - "image_id": 4, - "category_id": 2, - "segmentation": [], - "area": 222540.6008, - "bbox": [122.84, 283.66, 445.01, 500.08], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 7, - "image_id": 4, - "category_id": 2, - "segmentation": [], - "area": 321147.2111999999, - "bbox": [347.54, 155.88, 592.61, 541.92], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 8, - "image_id": 5, - "category_id": 2, - "segmentation": [], - "area": 969726.6356, - "bbox": [37.2, 666.4, 1063.46, 911.86], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 9, - "image_id": 6, - "category_id": 2, - "segmentation": [], - "area": 446568.5952, - "bbox": [105.23, 398.47, 524.16, 851.97], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 10, - "image_id": 6, - "category_id": 2, - "segmentation": [], - "area": 439902.1328, - "bbox": [585.6, 138.82, 417.04, 1054.82], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 11, - "image_id": 7, - "category_id": 1, - "segmentation": [], - "area": 266808.472, - "bbox": [320.94, 3.79, 319.6, 834.82], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 12, - "image_id": 8, - "category_id": 1, - "segmentation": [], - "area": 1711147.0188, - "bbox": [156.14, 570.92, 1101.89, 1552.92], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 13, - "image_id": 9, - "category_id": 2, - "segmentation": [], - "area": 350061.43840000004, - "bbox": [431.08, 169.9, 432.26, 809.84], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 14, - "image_id": 10, - "category_id": 2, - "segmentation": [], - "area": 975123.9674000001, - "bbox": [170.74, 665.17, 1077.89, 904.66], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 15, - "image_id": 11, - "category_id": 2, - "segmentation": [], - "area": 323659.9134, - "bbox": [340.39, 153.13, 594.81, 544.14], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 16, - "image_id": 11, - "category_id": 2, - "segmentation": [], - "area": 230117.52299999996, - "bbox": [713.8, 285.31, 448.31, 513.3], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 17, - "image_id": 12, - "category_id": 1, - "segmentation": [], - "area": 373458.2508, - "bbox": [155.79, 229.86, 757.86, 492.78], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - }, - { - "id": 18, - "image_id": 13, - "category_id": 1, - "segmentation": [], - "area": 267909.6806, - "bbox": [397.9, 2.18, 321.74, 832.69], - "iscrowd": 0, - "attributes": { "occluded": false, "rotation": 0.0 } - } - ] -} diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json b/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json deleted file mode 100644 index 7a3a7fc2cc..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/datasets/task_test-gt-skeletons_annotations_2024_02_02_14_17_50_coco keypoints 1.0-good/annotations/person_keypoints_default.json +++ /dev/null @@ -1 +0,0 @@ -{"licenses":[{"name":"","id":0,"url":""}],"info":{"contributor":"","date_created":"","description":"","url":"","version":"","year":""},"categories":[{"id":1,"name":"cat","supercategory":"","keypoints":["head","r front leg","l front leg","r back leg","l back leg","neck","torso"],"skeleton":[[7,4],[6,2],[6,1],[5,7],[7,6],[6,3]]},{"id":9,"name":"dog","supercategory":"","keypoints":["nose","l ear","r ear"],"skeleton":[[3,1],[2,1]]}],"images":[{"id":1,"width":1279,"height":854,"file_name":"img1.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":2,"width":800,"height":1039,"file_name":"img10.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":3,"width":900,"height":600,"file_name":"img11.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":4,"width":1280,"height":879,"file_name":"img12.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":5,"width":1280,"height":1920,"file_name":"img13.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":6,"width":1280,"height":1295,"file_name":"img2.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":7,"width":1279,"height":853,"file_name":"img3.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":8,"width":1280,"height":2278,"file_name":"img4.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":9,"width":1280,"height":1039,"file_name":"img5.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":10,"width":1280,"height":1920,"file_name":"img6.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":11,"width":1280,"height":879,"file_name":"img7.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":12,"width":1280,"height":853,"file_name":"img8.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0},{"id":13,"width":800,"height":853,"file_name":"img9.jpg","license":0,"flickr_url":"","coco_url":"","date_captured":0}],"annotations":[{"id":1,"image_id":1,"category_id":1,"segmentation":[],"area":26598.009200000004,"bbox":[276.84,456.97,112.28,236.89],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[343.77,456.97,2,389.12,683.69,2,326.35,687.92,2,290.69,693.86,2,359.15,685.99,2,342.5,527.96,2,276.84,634.98,2],"num_keypoints":7},{"id":2,"image_id":1,"category_id":1,"segmentation":[],"area":17348.673300000002,"bbox":[874.63,310.35,139.47,124.39],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[939.77,310.35,2,898.52,434.74,2,994.96,430.32,2,874.63,413.0,2,1014.1,432.35,2,987.86,353.88,2,1007.35,399.89,2],"num_keypoints":7},{"id":3,"image_id":1,"category_id":1,"segmentation":[],"area":13275.416700000005,"bbox":[377.52,253.64,193.83,68.49],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[377.52,253.64,2,424.44,306.56,2,410.48,303.45,2,519.36,319.3,2,536.8,322.13,2,437.73,263.27,2,571.35,292.16,2],"num_keypoints":7},{"id":4,"image_id":6,"category_id":9,"segmentation":[],"area":26851.028500000004,"bbox":[290.23,467.54,195.95,137.03],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[486.18,604.57,2,459.0,467.54,2,290.23,508.66,2],"num_keypoints":3},{"id":5,"image_id":6,"category_id":9,"segmentation":[],"area":17102.630999999998,"bbox":[681.32,181.06,277.55,61.62],"iscrowd":0,"attributes":{"occluded":false,"keyframe":false},"keypoints":[958.87,242.68,2,854.96,181.06,2,681.32,241.06,2],"num_keypoints":3}]} \ No newline at end of file diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json b/packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json deleted file mode 100644 index 617280c03e..0000000000 --- a/packages/examples/cvat/exchange-oracle/tests/utils/manifest_skeletons_from_boxes_local.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "data": { - "data_url": "http://127.0.0.1:9010/datasets/", - "boxes_url": "http://127.0.0.1:9010/datasets/task_test-boxes-to-skeletons_annotations_2024_02_02_13_18_32_coco%201.0-good/annotations/instances_default.json" - }, - "annotation": { - "labels": [ - { - "name": "cat", - "type": "skeleton", - "nodes": [ - "head", - "neck", - "torso", - "l front leg", - "r front leg", - "l back leg", - "r back leg" - ], - "joints": [ - [0, 1], - [1, 2], - [1, 3], - [1, 4], - [3, 5], - [3, 6] - ] - }, - { - "name": "dog", - "type": "skeleton", - "nodes": ["nose", "l ear", "r ear"], - "joints": [ - [0, 1], - [0, 2] - ] - } - ], - "description": "Brief task description", - "user_guide": "Task description (markdown)", - "type": "IMAGE_SKELETONS_FROM_BOXES", - "job_size": 10, - "max_time": 1800 - }, - "validation": { - "min_quality": 0.8, - "val_size": 2, - "gt_url": "http://127.0.0.1:9010/datasets/gt_skeletons/annotations/person_keypoints_default.json" - }, - "job_bounty": "0.05" -} From 4ce71da3c8b2e3393298f701b5b6ee49da3d6ce3 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 9 Feb 2024 18:52:49 +0200 Subject: [PATCH 36/82] Fix label mapping --- .../cvat/exchange-oracle/src/handlers/job_creation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index a525a4b7c9..2bdafefc11 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -323,8 +323,8 @@ def _validate_points_categories(self): raise DatasetValidationError("Point labels do not match job labels") self.input_points_dataset.transform( - "project_labels", dst_labels=[label.name for label in self.manifest.annotation.labels] - ) # TODO: fix the transform for skeletons + ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] + ) self.input_points_dataset.init_cache() def _validate_points_filenames(self): From 37a238153f0213adab90c847a0be0b6e3d808fc6 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 12 Feb 2024 10:50:13 +0200 Subject: [PATCH 37/82] Add extra skeleton validations in manifest --- .../cvat/exchange-oracle/src/core/manifest.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 35d7ce0c6b..fbdd483021 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -29,7 +29,7 @@ class LabelType(str, Enum, metaclass=BetterEnumMeta): class LabelInfo(BaseModel): - name: str + name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ type: LabelType = LabelType.plain @@ -60,9 +60,22 @@ def validate_type(cls, values: dict) -> dict: if values["type"] != LabelType.skeleton: raise ValueError(f"Label type must be {LabelType.skeleton}") - # TODO: validate label names (empty strings, repeats) - skeleton_name = values["name"] + + existing_names = set() + for node_name in values["nodes"]: + node_name = node_name.strip() + + if not node_name: + raise ValueError(f"Skeleton '{skeleton_name}': point name is empty") + + if node_name.lower() in existing_names: + raise ValueError( + f"Skeleton '{skeleton_name}' point {node_name}: label is duplicated" + ) + + existing_names.add(node_name.lower()) + nodes_count = len(values["nodes"]) joints = values["joints"] for joint_idx, joint in enumerate(joints): From a7ee995f3bd962d176dbb4bffa955259209eaa80 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 22 Feb 2024 12:36:15 +0200 Subject: [PATCH 38/82] Update .env template --- packages/examples/cvat/exchange-oracle/src/.env.template | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/.env.template b/packages/examples/cvat/exchange-oracle/src/.env.template index ee8dba1feb..ac1a2daa64 100644 --- a/packages/examples/cvat/exchange-oracle/src/.env.template +++ b/packages/examples/cvat/exchange-oracle/src/.env.template @@ -35,10 +35,12 @@ PROCESS_RECORDING_ORACLE_WEBHOOKS_CHUNK_SIZE= TRACK_COMPLETED_PROJECTS_INT= TRACK_COMPLETED_PROJECTS_CHUNK_SIZE= TRACK_COMPLETED_TASKS_INT= -RETRIEVE_ANNOTATIONS_INT= -RETRIEVE_ANNOTATIONS_CHUNK_SIZE= +TRACK_COMPLETED_ESCROWS_INT= +TRACK_COMPLETED_ESCROWS_CHUNK_SIZE= PROCESS_JOB_LAUNCHER_WEBHOOKS_INT= TRACK_CREATING_TASKS_INT= +REJECTED_PROJECTS_CHUNK_SIZE= +ACCEPTED_PROJECTS_CHUNK_SIZE= # CVAT Config @@ -73,5 +75,6 @@ HUMAN_APP_SIGNATURE= # Localhost +LOCALHOST_RECORDING_ORACLE_ADDRESS= LOCALHOST_RECORDING_ORACLE_URL= LOCALHOST_JOB_LAUNCHER_URL= \ No newline at end of file From e2c034ebc50eea4f12fd418c35356f484e5f81d8 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 22 Feb 2024 12:36:33 +0200 Subject: [PATCH 39/82] Update several comments --- .../exchange-oracle/src/core/tasks/skeletons_from_boxes.py | 1 - .../examples/cvat/exchange-oracle/src/endpoints/webhook.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py index b6b680c19a..99eaacb285 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -11,7 +11,6 @@ SkeletonBboxMapping = Dict[int, int] -# TODO: migrate to pydantic @frozen(kw_only=True) class RoiInfo: original_image_key: int diff --git a/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py b/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py index 4c4cf1dbd6..eb4be7b6ee 100644 --- a/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/endpoints/webhook.py @@ -19,13 +19,12 @@ async def receive_oracle_webhook( human_signature: Union[str, None] = Header(default=None), ) -> OracleWebhookResponse: try: - # TODO: remove mock + # TODO: remove mock once implemented in launcher if not human_signature: human_signature = "launcher-{}".format(utcnow().timestamp()) sender_type = OracleWebhookTypes.job_launcher else: - # TODO: add allowed sender type checks sender_type = await validate_oracle_webhook_signature(request, human_signature, webhook) with SessionLocal.begin() as session: From 7c8d9766ee60e43ec9860f92409cf20314ea42a6 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 22 Feb 2024 13:06:28 +0200 Subject: [PATCH 40/82] Fix failing tests --- .../cvat/exchange-oracle/src/core/manifest.py | 27 ++--- ...ons.py => test_track_completed_escrows.py} | 24 ++-- .../recording-oracle/src/core/manifest.py | 108 +++++++++++++++--- 3 files changed, 120 insertions(+), 39 deletions(-) rename packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/{test_retrieve_annotations.py => test_track_completed_escrows.py} (92%) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index fbdd483021..f41e0e08f0 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,6 +1,6 @@ from decimal import Decimal from enum import Enum -from typing import Annotated, List, Literal, Optional, Tuple, Union +from typing import Annotated, Any, List, Literal, Optional, Tuple, Union from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -28,18 +28,18 @@ class LabelType(str, Enum, metaclass=BetterEnumMeta): skeleton = "skeleton" -class LabelInfo(BaseModel): +class LabelInfoBase(BaseModel): name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ type: LabelType = LabelType.plain -class PlainLabelInfo(LabelInfo): +class PlainLabelInfo(LabelInfoBase): type: Literal[LabelType.plain] -class SkeletonLabelInfo(LabelInfo): +class SkeletonLabelInfo(LabelInfoBase): type: Literal[LabelType.skeleton] nodes: List[str] = Field(min_items=1) @@ -89,13 +89,13 @@ def validate_type(cls, values: dict) -> dict: return values -_Label = Annotated[Union[PlainLabelInfo, SkeletonLabelInfo], Field(discriminator="type")] +LabelInfo = Annotated[Union[PlainLabelInfo, SkeletonLabelInfo], Field(discriminator="type")] class AnnotationInfo(BaseModel): type: TaskType - labels: list[_Label] + labels: list[LabelInfo] = Field(min_items=1) "Label declarations with accepted annotation types" description: str = "" @@ -131,15 +131,16 @@ class TaskManifest(BaseModel): "Assignment bounty, a decimal value in HMT" -def parse_manifest(manifest: dict) -> TaskManifest: +def parse_manifest(manifest: Any) -> TaskManifest: # Add default value for labels, if none provided. # pydantic can't do this for tagged unions - try: - labels = manifest["annotation"]["labels"] - for label_info in labels: - label_info["type"] = label_info.get("type", LabelType.plain) - except KeyError: - pass + if isinstance(manifest, dict): + try: + labels = manifest["annotation"]["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", LabelType.plain) + except KeyError: + pass return TaskManifest.parse_obj(manifest) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py similarity index 92% rename from packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py rename to packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py index 53cbaeb940..84dedc6f29 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py @@ -99,10 +99,10 @@ def test_retrieve_annotations(self): with ( open("tests/utils/manifest.json") as data, - patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api") as mock_cvat_api, - patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client") as mock_cloud_client, + patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.completed_escrows.validate_escrow"), + patch("src.handlers.completed_escrows.cvat_api") as mock_cvat_api, + patch("src.handlers.completed_escrows.cloud_client") as mock_cloud_client, ): manifest = json.load(data) mock_get_manifest.return_value = manifest @@ -247,11 +247,11 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): with ( open("tests/utils/manifest.json") as data, - patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), - patch("src.crons.state_trackers.cvat_api.get_job_annotations") as mock_annotations, - patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_client.S3Client") as mock_S3Client, + patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.completed_escrows.validate_escrow"), + patch("src.handlers.completed_escrows.cvat_api"), + patch("src.handlers.completed_escrows.cvat_api.get_job_annotations") as mock_annotations, + patch("src.handlers.completed_escrows.cloud_client.S3Client") as mock_S3Client, ): manifest = json.load(data) mock_get_manifest.return_value = manifest @@ -331,9 +331,9 @@ def test_retrieve_annotations_error_uploading_files(self): with ( open("tests/utils/manifest.json") as data, - patch("src.crons.state_trackers.get_escrow_manifest") as mock_get_manifest, - patch("src.crons.state_trackers.cvat_api"), - patch("src.crons.state_trackers.validate_escrow"), + patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, + patch("src.handlers.completed_escrows.validate_escrow"), + patch("src.handlers.completed_escrows.cvat_api"), ): manifest = json.load(data) mock_get_manifest.return_value = manifest diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 00bd265b82..f41e0e08f0 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -1,10 +1,12 @@ from decimal import Decimal -from typing import Optional +from enum import Enum +from typing import Annotated, Any, List, Literal, Optional, Tuple, Union from pydantic import AnyUrl, BaseModel, Field, root_validator from src.core.config import Config from src.core.types import TaskType +from src.utils.enums import BetterEnumMeta class DataInfo(BaseModel): @@ -12,16 +14,88 @@ class DataInfo(BaseModel): "Bucket URL, s3 only, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + points_url: Optional[AnyUrl] = None + "A path to an archive with a set of points in COCO Keypoints format, " + "which provides information about all objects on images" -class LabelInfo(BaseModel): - name: str + boxes_url: Optional[AnyUrl] = None + "A path to an archive with a set of boxes in COCO Instances format, " + "which provides information about all objects on images" + + +class LabelType(str, Enum, metaclass=BetterEnumMeta): + plain = "plain" + skeleton = "skeleton" + + +class LabelInfoBase(BaseModel): + name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ + type: LabelType = LabelType.plain + + +class PlainLabelInfo(LabelInfoBase): + type: Literal[LabelType.plain] + + +class SkeletonLabelInfo(LabelInfoBase): + type: Literal[LabelType.skeleton] + + nodes: List[str] = Field(min_items=1) + """ + A list of node label names (only points are supposed to be nodes). + Example: + [ + "left hand", "torso", "right hand", "head" + ] + """ + + joints: List[Tuple[int, int]] + "A list of node adjacency, e.g. [[0, 1], [1, 2], [1, 3]]" + + @root_validator + @classmethod + def validate_type(cls, values: dict) -> dict: + if values["type"] != LabelType.skeleton: + raise ValueError(f"Label type must be {LabelType.skeleton}") + + skeleton_name = values["name"] + + existing_names = set() + for node_name in values["nodes"]: + node_name = node_name.strip() + + if not node_name: + raise ValueError(f"Skeleton '{skeleton_name}': point name is empty") + + if node_name.lower() in existing_names: + raise ValueError( + f"Skeleton '{skeleton_name}' point {node_name}: label is duplicated" + ) + + existing_names.add(node_name.lower()) + + nodes_count = len(values["nodes"]) + joints = values["joints"] + for joint_idx, joint in enumerate(joints): + for v in joint: + if not (0 <= v < nodes_count): + raise ValueError( + f"Skeleton '{skeleton_name}' joint #{joint_idx}: invalid value. " + f"Expected a number in the range [0; {nodes_count - 1}]" + ) + + return values + + +LabelInfo = Annotated[Union[PlainLabelInfo, SkeletonLabelInfo], Field(discriminator="type")] + class AnnotationInfo(BaseModel): type: TaskType - labels: list[LabelInfo] + labels: list[LabelInfo] = Field(min_items=1) "Label declarations with accepted annotation types" description: str = "" @@ -33,18 +107,9 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: Optional[int] + max_time: int = Field(default_factory=lambda: Config.core_config.default_assignment_time) "Maximum time per job (assignment) for an annotator, in seconds" - @root_validator - @classmethod - def validate_type(cls, values: dict) -> dict: - if values["type"] == TaskType.image_label_binary: - if len(values["labels"]) != 2: - raise ValueError("Binary classification requires 2 labels") - - return values - class ValidationInfo(BaseModel): min_quality: float = Field(ge=0) @@ -64,3 +129,18 @@ class TaskManifest(BaseModel): job_bounty: Decimal = Field(ge=0) "Assignment bounty, a decimal value in HMT" + + +def parse_manifest(manifest: Any) -> TaskManifest: + # Add default value for labels, if none provided. + # pydantic can't do this for tagged unions + + if isinstance(manifest, dict): + try: + labels = manifest["annotation"]["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", LabelType.plain) + except KeyError: + pass + + return TaskManifest.parse_obj(manifest) From 0b8648de76b543bc48daa1a5c46ef0c8a968232e Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 22 Feb 2024 13:08:00 +0200 Subject: [PATCH 41/82] Update enum name --- .../cvat/exchange-oracle/src/core/manifest.py | 14 +++++++------- .../cvat/recording-oracle/src/core/manifest.py | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index f41e0e08f0..6d4e434600 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -23,7 +23,7 @@ class DataInfo(BaseModel): "which provides information about all objects on images" -class LabelType(str, Enum, metaclass=BetterEnumMeta): +class LabelTypes(str, Enum, metaclass=BetterEnumMeta): plain = "plain" skeleton = "skeleton" @@ -32,15 +32,15 @@ class LabelInfoBase(BaseModel): name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ - type: LabelType = LabelType.plain + type: LabelTypes = LabelTypes.plain class PlainLabelInfo(LabelInfoBase): - type: Literal[LabelType.plain] + type: Literal[LabelTypes.plain] class SkeletonLabelInfo(LabelInfoBase): - type: Literal[LabelType.skeleton] + type: Literal[LabelTypes.skeleton] nodes: List[str] = Field(min_items=1) """ @@ -57,8 +57,8 @@ class SkeletonLabelInfo(LabelInfoBase): @root_validator @classmethod def validate_type(cls, values: dict) -> dict: - if values["type"] != LabelType.skeleton: - raise ValueError(f"Label type must be {LabelType.skeleton}") + if values["type"] != LabelTypes.skeleton: + raise ValueError(f"Label type must be {LabelTypes.skeleton}") skeleton_name = values["name"] @@ -139,7 +139,7 @@ def parse_manifest(manifest: Any) -> TaskManifest: try: labels = manifest["annotation"]["labels"] for label_info in labels: - label_info["type"] = label_info.get("type", LabelType.plain) + label_info["type"] = label_info.get("type", LabelTypes.plain) except KeyError: pass diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index f41e0e08f0..6d4e434600 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -23,7 +23,7 @@ class DataInfo(BaseModel): "which provides information about all objects on images" -class LabelType(str, Enum, metaclass=BetterEnumMeta): +class LabelTypes(str, Enum, metaclass=BetterEnumMeta): plain = "plain" skeleton = "skeleton" @@ -32,15 +32,15 @@ class LabelInfoBase(BaseModel): name: str = Field(min_length=1) # https://opencv.github.io/cvat/docs/api_sdk/sdk/reference/models/label/ - type: LabelType = LabelType.plain + type: LabelTypes = LabelTypes.plain class PlainLabelInfo(LabelInfoBase): - type: Literal[LabelType.plain] + type: Literal[LabelTypes.plain] class SkeletonLabelInfo(LabelInfoBase): - type: Literal[LabelType.skeleton] + type: Literal[LabelTypes.skeleton] nodes: List[str] = Field(min_items=1) """ @@ -57,8 +57,8 @@ class SkeletonLabelInfo(LabelInfoBase): @root_validator @classmethod def validate_type(cls, values: dict) -> dict: - if values["type"] != LabelType.skeleton: - raise ValueError(f"Label type must be {LabelType.skeleton}") + if values["type"] != LabelTypes.skeleton: + raise ValueError(f"Label type must be {LabelTypes.skeleton}") skeleton_name = values["name"] @@ -139,7 +139,7 @@ def parse_manifest(manifest: Any) -> TaskManifest: try: labels = manifest["annotation"]["labels"] for label_info in labels: - label_info["type"] = label_info.get("type", LabelType.plain) + label_info["type"] = label_info.get("type", LabelTypes.plain) except KeyError: pass From b8a82dc5a5ef4c2456e80bb9e7c8c9f5c19c4269 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 12 Feb 2024 16:09:08 +0200 Subject: [PATCH 42/82] Add more annotation validations for skeletons --- .../src/handlers/job_creation.py | 340 ++++++++++++------ .../exchange-oracle/src/utils/annotations.py | 4 + 2 files changed, 228 insertions(+), 116 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 2bdafefc11..141b963d59 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -4,12 +4,12 @@ import random import uuid from contextlib import ExitStack -from dataclasses import dataclass +from dataclasses import dataclass, field from itertools import chain, groupby from logging import Logger from math import ceil from tempfile import TemporaryDirectory -from typing import Dict, List, Sequence, Tuple, TypeVar, Union, cast +from typing import Dict, List, Optional, Sequence, Tuple, TypeVar, Union, cast import cv2 import datumaro as dm @@ -32,7 +32,7 @@ from src.log import ROOT_LOGGER_NAME from src.services.cloud import CloudProviders, StorageClient from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url, parse_bucket_url -from src.utils.annotations import ProjectLabels +from src.utils.annotations import ProjectLabels, is_point_in_bbox from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger @@ -932,6 +932,28 @@ def build(self): class SkeletonsFromBoxesTaskBuilder: + @dataclass + class _ExcludedAnnotationsInfo: + @dataclass + class _ExcludedAnnotationInfo: + message: str + sample_id: str = field(kw_only=True) + sample_subset: str = field(kw_only=True) + + errors: List[_ExcludedAnnotationInfo] = field(default_factory=list) + + excluded_count: int = 0 + "The number of excluded annotations. Can be different from len(error_messages)" + + total_count: int = 0 + + def add_error(self, message: str, *, sample_id: str, sample_subset: str): + self.errors.append( + self._ExcludedAnnotationInfo( + message=message, sample_id=sample_id, sample_subset=sample_subset + ) + ) + @dataclass class _JobParams: label_id: int @@ -945,17 +967,22 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.logger: Logger = NullLogger() - self.input_gt_data: _MaybeUnset[bytes] = _unset - self.input_boxes_data: _MaybeUnset[bytes] = _unset + self._input_gt_data: _MaybeUnset[bytes] = _unset + self._input_boxes_data: _MaybeUnset[bytes] = _unset - # Computed values - self.input_filenames: _MaybeUnset[Sequence[str]] = _unset - self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset - self.boxes_dataset: _MaybeUnset[dm.Dataset] = _unset + self._data_filenames: _MaybeUnset[Sequence[str]] = _unset + self._gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self._boxes_dataset: _MaybeUnset[dm.Dataset] = _unset - self.roi_filenames: _MaybeUnset[Dict[int, str]] = _unset - self.job_params: _MaybeUnset[List[self._JobParams]] = _unset - self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self._skeleton_bbox_mapping: _MaybeUnset[ + skeletons_from_boxes_task.SkeletonBboxMapping + ] = _unset + self._roi_infos: _MaybeUnset[skeletons_from_boxes_task.RoiInfos] = _unset + self._roi_filenames: _MaybeUnset[Dict[int, str]] = _unset + self._job_params: _MaybeUnset[List[self._JobParams]] = _unset + + self._excluded_gt_info: _MaybeUnset[self._ExcludedAnnotationsInfo] = _unset + self._excluded_boxes_info: _MaybeUnset[self._ExcludedAnnotationsInfo] = _unset # Configuration / constants self.job_size_mult = 6 @@ -1021,14 +1048,14 @@ def _download_input_data(self): data_bucket.url.bucket_name, prefix=data_bucket.url.path, ) - self.input_filenames = filter_image_files(data_filenames) + self._data_filenames = filter_image_files(data_filenames) - self.input_gt_data = gt_storage_client.download_file( + self._input_gt_data = gt_storage_client.download_file( gt_bucket.url.bucket_name, gt_bucket.url.path, ) - self.input_boxes_data = boxes_storage_client.download_file( + self._input_boxes_data = boxes_storage_client.download_file( boxes_bucket.url.bucket_name, boxes_bucket.url.path, ) @@ -1043,24 +1070,24 @@ def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm return dm.Dataset.import_from(annotation_filename, format=dataset_format) def _parse_gt(self): - assert self.input_gt_data is not _unset + assert self._input_gt_data is not _unset - self.gt_dataset = self._parse_dataset( - self.input_gt_data, + self._gt_dataset = self._parse_dataset( + self._input_gt_data, dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], ) def _parse_boxes(self): - assert self.input_boxes_data is not _unset + assert self._input_boxes_data is not _unset - self.boxes_dataset = self._parse_dataset( - self.input_boxes_data, dataset_format=self.boxes_format + self._boxes_dataset = self._parse_dataset( + self._input_boxes_data, dataset_format=self.boxes_format ) def _validate_gt_labels(self): gt_labels = set( (label.name, label.parent) - for label in self.gt_dataset.categories()[dm.AnnotationType.label] + for label in self._gt_dataset.categories()[dm.AnnotationType.label] ) manifest_labels = set() @@ -1082,15 +1109,15 @@ def _validate_gt_labels(self): ) # Reorder labels to match the manifest - self.gt_dataset.transform( + self._gt_dataset.transform( ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self.gt_dataset.init_cache() + self._gt_dataset.init_cache() def _validate_gt_filenames(self): - gt_filenames = set(s.id + s.media.ext for s in self.gt_dataset) + gt_filenames = set(s.id + s.media.ext for s in self._gt_dataset) - known_data_filenames = set(self.input_filenames) + known_data_filenames = set(self._data_filenames) matched_gt_filenames = gt_filenames.intersection(known_data_filenames) if len(gt_filenames) != len(matched_gt_filenames): @@ -1108,19 +1135,67 @@ def _validate_gt_filenames(self): f"at least {self.manifest.validation.val_size} required." ) + def _validate_gt_annotations(self): + def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox) -> Optional[str]: + for element in skeleton.elements: + # This is what Datumaro expected to parse + assert isinstance(element, dm.Points) + assert len(element.points) == 2 and len(element.visibility) == 1 + + if element.visibility[0] != dm.Points.Visibility.visible: + continue + + px, py = element.points[:2] + if not is_point_in_bbox(px, py, sample_bbox): + raise DatasetValidationError("skeleton point is outside the image") + + excluded_gt_info = self._ExcludedAnnotationsInfo() + + label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + for gt_sample in self._gt_dataset: + # Could fail on this as well + img_h, img_w = gt_sample.media_as(dm.Image).size + sample_bbox = dm.Bbox(0, 0, w=img_w, h=img_h) + + sample_skeletons = [a for a in gt_sample.annotations if isinstance(a, dm.Skeleton)] + valid_skeletons = [] + for skeleton in sample_skeletons: + try: + _validate_skeleton(skeleton, sample_bbox=sample_bbox) + except DatasetValidationError as error: + excluded_gt_info.add_error( + "Sample '{}': skeleton #{} ({}) skipped - {}".format( + gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + + excluded_gt_info.excluded_count += len(sample_skeletons) - len(valid_skeletons) + excluded_gt_info.total_count += len(sample_skeletons) + + if len(valid_skeletons) != len(sample_skeletons): + # Skeleton boxes can be in the list as well with the same ids / groups + skeleton_ids = set(a.id for a in valid_skeletons) - {0} + self._gt_dataset.put( + gt_sample.wrap( + annotations=[a for a in gt_sample.annotations if a.id in skeleton_ids] + ) + ) + + self._excluded_gt_info = excluded_gt_info + def _validate_gt(self): - assert self.input_filenames is not _unset - assert self.gt_dataset is not _unset + assert self._data_filenames is not _unset + assert self._gt_dataset is not _unset self._validate_gt_filenames() self._validate_gt_labels() - - # TODO: check coordinates - # TODO: check there are matching pairs of bbox/skeleton in GT - # TODO: pass discarded/total down + self._validate_gt_annotations() def _validate_boxes_categories(self): - boxes_dataset_categories = self.boxes_dataset.categories() + boxes_dataset_categories = self._boxes_dataset.categories() boxes_dataset_label_cat: dm.LabelCategories = boxes_dataset_categories[ dm.AnnotationType.label ] @@ -1131,15 +1206,15 @@ def _validate_boxes_categories(self): raise DatasetValidationError("Bbox labels do not match job labels") # Reorder labels to match the manifest - self.boxes_dataset.transform( + self._boxes_dataset.transform( ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self.boxes_dataset.init_cache() + self._boxes_dataset.init_cache() def _validate_boxes_filenames(self): - boxes_filenames = set(sample.id + sample.media.ext for sample in self.boxes_dataset) + boxes_filenames = set(sample.id + sample.media.ext for sample in self._boxes_dataset) - known_data_filenames = set(self.input_filenames) + known_data_filenames = set(self._data_filenames) matched_boxes_filenames = boxes_filenames.intersection(known_data_filenames) if len(known_data_filenames) != len(matched_boxes_filenames): @@ -1160,48 +1235,67 @@ def _validate_boxes_filenames(self): ) def _validate_boxes_annotations(self): - label_cat: dm.LabelCategories = self.boxes_dataset.categories()[dm.AnnotationType.label] + excluded_boxes_info = self._ExcludedAnnotationsInfo() - # TODO: check for excluded boxes count - excluded_samples = [] - for sample in self.boxes_dataset: - # Could fail on this as well - image_h, image_w = sample.image.size + label_cat: dm.LabelCategories = self._boxes_dataset.categories()[dm.AnnotationType.label] - for bbox in sample.annotations: - # Could fail on this as well - if not isinstance(bbox, dm.Bbox): - continue + for sample in self._boxes_dataset: + # Could fail on this as well + image_h, image_w = sample.media_as(dm.Image).size + sample_boxes = [a for a in sample.annotations if isinstance(a, dm.Bbox)] + valid_boxes = [] + for bbox in sample_boxes: if not ( (0 <= bbox.x < bbox.x + bbox.w <= image_w) and (0 <= bbox.y < bbox.y + bbox.h <= image_h) ): - message = "Sample '{}': bbox #{} ({}) skipped - " "invalid coordinates".format( - sample.id, bbox.id, label_cat[bbox.label].name + excluded_boxes_info.add_error( + "Sample '{}': bbox #{} ({}) skipped - invalid coordinates".format( + sample.id, bbox.id, label_cat[bbox.label].name + ), + sample_id=sample.id, + sample_subset=sample.subset, ) - excluded_samples.append(((sample.id, sample.subset), message)) - if len(excluded_samples) > len(self.boxes_dataset) * self.max_discarded_threshold: - raise DatasetValidationError( - "Too many samples discarded, canceling job creation. Errors: {}".format( - self._format_list([message for _, message in excluded_samples]) + valid_boxes.append(bbox) + + excluded_boxes_info.excluded_count += len(sample_boxes) - len(valid_boxes) + excluded_boxes_info.total_count += len(sample_boxes) + + if len(valid_boxes) != len(sample.annotations): + self._boxes_dataset.put(sample.wrap(annotations=valid_boxes)) + + if ( + excluded_boxes_info.excluded_count + > excluded_boxes_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many boxes discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_boxes_info.errors] + ) ) ) - for excluded_sample, _ in excluded_samples: - self.boxes_dataset.remove(*excluded_sample) + excluded_samples = set((e.sample_id, e.sample_subset) for e in excluded_boxes_info.errors) + for excluded_sample in excluded_samples: + self._boxes_dataset.remove(*excluded_sample) if excluded_samples: self.logger.warning( - "Some samples were excluded due to errors found: {}".format( - self._format_list([m for _, m in excluded_samples], separator="\n") + "Some boxes were excluded due to errors found: {}".format( + self._format_list( + [e.message for e in excluded_boxes_info.errors], separator="\n" + ) ) ) + self._excluded_boxes_info = excluded_boxes_info + def _validate_boxes(self): - assert self.input_filenames is not _unset - assert self.boxes_dataset is not _unset + assert self._data_filenames is not _unset + assert self._boxes_dataset is not _unset self._validate_boxes_categories() self._validate_boxes_filenames() @@ -1223,33 +1317,30 @@ def _match_boxes(self, a: BboxCoords, b: BboxCoords): return bbox_iou(a, b) > 0 def _prepare_gt(self): - assert self.input_filenames is not _unset - assert self.boxes_dataset is not _unset - assert self.gt_dataset is not _unset + assert self._data_filenames is not _unset + assert self._boxes_dataset is not _unset + assert self._gt_dataset is not _unset assert [ label.name - for label in self.gt_dataset.categories()[dm.AnnotationType.label] + for label in self._gt_dataset.categories()[dm.AnnotationType.label] if not label.parent ] == [label.name for label in self.manifest.annotation.labels] assert [ label.name - for label in self.boxes_dataset.categories()[dm.AnnotationType.label] + for label in self._boxes_dataset.categories()[dm.AnnotationType.label] if not label.parent ] == [label.name for label in self.manifest.annotation.labels] updated_gt_dataset = dm.Dataset( - categories=self.gt_dataset.categories(), media_type=dm.Image + categories=self._gt_dataset.categories(), media_type=dm.Image ) - gt_label_cat: dm.LabelCategories = self.gt_dataset.categories()[dm.AnnotationType.label] + gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] - excluded_skeletons_messages = [] - total_skeletons = 0 gt_count_per_class = {} - skeleton_bbox_mapping = {} # skeleton id -> bbox id - for gt_sample in self.gt_dataset: - boxes_sample = self.boxes_dataset.get(gt_sample.id, gt_sample.subset) + for gt_sample in self._gt_dataset: + boxes_sample = self._boxes_dataset.get(gt_sample.id, gt_sample.subset) # Samples could be discarded, so we just skip them without an error if not boxes_sample: continue @@ -1261,8 +1352,6 @@ def _prepare_gt(self): if not gt_skeletons: continue - total_skeletons += len(gt_skeletons) - # Find unambiguous gt skeleton - input bbox pairs matched_skeletons = [] visited_skeletons = set() @@ -1271,12 +1360,15 @@ def _prepare_gt(self): if len(visited_skeletons) == len(gt_skeletons): # Handle unmatched boxes - excluded_skeletons_messages.append( + self._excluded_gt_info.add_error( "Sample '{}': GT skeleton #{} ({}) skipped - " "no matching boxes found".format( gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name - ) + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, ) + self._excluded_gt_info.excluded_count += 1 continue matched_boxes: List[dm.Bbox] = [] @@ -1297,26 +1389,32 @@ def _prepare_gt(self): if len(matched_boxes) > 1: # Handle ambiguous matches - excluded_skeletons_messages.append( + self._excluded_gt_info.add_error( "Sample '{}': GT skeleton #{} ({}) skipped - " "too many matching boxes ({}) found".format( gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name, len(matched_boxes), - ) + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, ) + self._excluded_gt_info.excluded_count += 1 continue elif len(matched_boxes) == 0: # Handle unmatched boxes - excluded_skeletons_messages.append( + self._excluded_gt_info.add_error( "Sample '{}': GT skeleton #{} ({}) skipped - " "no matching boxes found".format( gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name, - ) + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, ) + self._excluded_gt_info.excluded_count += 1 continue # TODO: maybe check if the top match is good enough @@ -1334,15 +1432,24 @@ def _prepare_gt(self): updated_gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) - if len(skeleton_bbox_mapping) < (1 - self.max_discarded_threshold) * total_skeletons: + if ( + len(skeleton_bbox_mapping) + < (1 - self.max_discarded_threshold) * self._excluded_gt_info.total_count + ): raise DatasetValidationError( "Too many GT skeletons discarded ({} out of {}). " "Please make sure each GT skeleton matches exactly 1 bbox".format( - total_skeletons - len(skeleton_bbox_mapping), total_skeletons + self._excluded_gt_info.total_count - len(skeleton_bbox_mapping), + self._excluded_gt_info.total_count, + ) + ) + + if self._excluded_gt_info.errors: + self.logger.warning( + "Some GT annotations were excluded due to errors found: {}".format( + self._format_list(self._excluded_gt_info.errors, separator="\n") ) ) - elif excluded_skeletons_messages: - self.logger.warning(self._format_list(excluded_skeletons_messages, separator="\n")) labels_with_few_gt = [ gt_label_cat[label_id] @@ -1356,15 +1463,15 @@ def _prepare_gt(self): ) ) - self.gt_dataset = updated_gt_dataset - self.skeleton_bbox_mapping = skeleton_bbox_mapping + self._gt_dataset = updated_gt_dataset + self._skeleton_bbox_mapping = skeleton_bbox_mapping def _prepare_roi_infos(self): - assert self.gt_dataset is not _unset - assert self.boxes_dataset is not _unset + assert self._gt_dataset is not _unset + assert self._boxes_dataset is not _unset rois: List[skeletons_from_boxes_task.RoiInfo] = [] - for sample in self.boxes_dataset: + for sample in self._boxes_dataset: for bbox in sample.annotations: if not isinstance(bbox, dm.Bbox): continue @@ -1396,24 +1503,24 @@ def _prepare_roi_infos(self): ) ) - self.roi_infos = rois + self._roi_infos = rois def _mangle_filenames(self): """ Mangle filenames in the dataset to make them less recognizable by annotators and hide private dataset info """ - assert self.roi_infos is not _unset + assert self._roi_infos is not _unset # TODO: maybe add different names for the same GT images in # different jobs to make them even less recognizable - self.roi_filenames = { - roi_info.bbox_id: str(uuid.uuid4()) + self.roi_file_ext for roi_info in self.roi_infos + self._roi_filenames = { + roi_info.bbox_id: str(uuid.uuid4()) + self.roi_file_ext for roi_info in self._roi_infos } def _prepare_job_params(self): - assert self.roi_infos is not _unset - assert self.skeleton_bbox_mapping is not _unset + assert self._roi_infos is not _unset + assert self._skeleton_bbox_mapping is not _unset # Make job layouts wrt. manifest params # 1 job per task, 1 task for each point label @@ -1426,17 +1533,17 @@ def _prepare_job_params(self): job_params: List[self._JobParams] = [] - roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self.roi_infos} + roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in self._roi_infos} for label_id, _ in enumerate(self.manifest.annotation.labels): label_gt_roi_ids = set( roi_id - for roi_id in self.skeleton_bbox_mapping.values() + for roi_id in self._skeleton_bbox_mapping.values() if roi_info_by_id[roi_id].bbox_label == label_id ) label_data_roi_ids = [ roi_info.bbox_id - for roi_info in self.roi_infos + for roi_info in self._roi_infos if roi_info.bbox_label == label_id if roi_info.bbox_id not in label_gt_roi_ids ] @@ -1457,7 +1564,7 @@ def _prepare_job_params(self): job_params.append(self._JobParams(label_id=label_id, roi_ids=job_roi_ids)) - self.job_params = job_params + self._job_params = job_params def _prepare_job_labels(self): self.point_labels = {} @@ -1475,23 +1582,23 @@ def _upload_task_meta(self): file_list = [] file_list.append( - (serializer.serialize_bbox_annotations(self.boxes_dataset), layout.BOXES_FILENAME) + (serializer.serialize_bbox_annotations(self._boxes_dataset), layout.BOXES_FILENAME) ) file_list.append( ( - serializer.serialize_gt_annotations(self.gt_dataset), + serializer.serialize_gt_annotations(self._gt_dataset), layout.GT_FILENAME, ) ) file_list.append( ( - serializer.serialize_skeleton_bbox_mapping(self.skeleton_bbox_mapping), + serializer.serialize_skeleton_bbox_mapping(self._skeleton_bbox_mapping), layout.SKELETON_BBOX_MAPPING_FILENAME, ) ) - file_list.append((serializer.serialize_roi_info(self.roi_infos), layout.ROI_INFO_FILENAME)) + file_list.append((serializer.serialize_roi_info(self._roi_infos), layout.ROI_INFO_FILENAME)) file_list.append( - (serializer.serialize_roi_filenames(self.roi_filenames), layout.ROI_FILENAMES_FILENAME) + (serializer.serialize_roi_filenames(self._roi_filenames), layout.ROI_FILENAMES_FILENAME) ) file_list.append( (serializer.serialize_point_labels(self.point_labels), layout.POINT_LABELS_FILENAME) @@ -1551,10 +1658,9 @@ def _draw_roi_bbox(self, roi_image: np.ndarray, bbox: dm.Bbox) -> np.ndarray: ) def _extract_and_upload_rois(self): - assert self.roi_filenames is not _unset - assert self.roi_infos is not _unset + assert self._roi_filenames is not _unset + assert self._roi_infos is not _unset - # TODO: optimize downloading, this implementation won't work for big datasets src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) src_prefix = "" dst_bucket = self.oracle_data_bucket @@ -1563,25 +1669,27 @@ def _extract_and_upload_rois(self): dst_client = self._make_cloud_storage_client(dst_bucket) image_id_to_filename = { - sample.attributes["id"]: sample.image.path for sample in self.boxes_dataset + sample.attributes["id"]: sample.image.path for sample in self._boxes_dataset } - filename_to_sample = {sample.image.path: sample for sample in self.boxes_dataset} + filename_to_sample = {sample.image.path: sample for sample in self._boxes_dataset} _roi_info_key = lambda e: e.original_image_key roi_info_by_image: Dict[str, Sequence[skeletons_from_boxes_task.RoiInfo]] = { image_id_to_filename[image_id]: list(g) - for image_id, g in groupby(sorted(self.roi_infos, key=_roi_info_key), key=_roi_info_key) + for image_id, g in groupby( + sorted(self._roi_infos, key=_roi_info_key), key=_roi_info_key + ) } bbox_by_id = { bbox.id: bbox - for sample in self.boxes_dataset + for sample in self._boxes_dataset for bbox in sample.annotations if isinstance(bbox, dm.Bbox) } - for filename in self.input_filenames: + for filename in self._data_filenames: image_roi_infos = roi_info_by_image.get(filename, []) if not image_roi_infos: continue @@ -1606,7 +1714,7 @@ def _extract_and_upload_rois(self): if self.embed_bbox_in_roi_image: roi_pixels = self._draw_roi_bbox(roi_pixels, bbox_by_id[roi_info.bbox_id]) - filename = self.roi_filenames[roi_info.bbox_id] + filename = self._roi_filenames[roi_info.bbox_id] roi_bytes = encode_image(roi_pixels, os.path.splitext(filename)[-1]) dst_client.create_file( @@ -1618,14 +1726,14 @@ def _extract_and_upload_rois(self): ) def _create_on_cvat(self): - assert self.job_params is not _unset + assert self._job_params is not _unset assert self.point_labels is not _unset _job_params_label_key = lambda ts: ts.label_id jobs_by_skeleton_label = { skeleton_label_id: list(g) for skeleton_label_id, g in groupby( - sorted(self.job_params, key=_job_params_label_key), key=_job_params_label_key + sorted(self._job_params, key=_job_params_label_key), key=_job_params_label_key ) } @@ -1663,7 +1771,7 @@ def _create_on_cvat(self): skeleton_label_filenames.append( [ compose_data_bucket_filename( - self.escrow_address, self.chain_id, self.roi_filenames[roi_id] + self.escrow_address, self.chain_id, self._roi_filenames[roi_id] ) for roi_id in skeleton_label_job.roi_ids ] diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py index 6035876f2b..94d63d55b4 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -337,3 +337,7 @@ def transform_item(self, item): annotations.append(ann) return item.wrap(annotations=annotations) + + +def is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: + return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) From b55879a0b4367306b5d562557cb3e9d7b143fb03 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 12 Feb 2024 19:07:07 +0200 Subject: [PATCH 43/82] Extend input annotations validation --- .../src/handlers/job_creation.py | 578 +++++++++++------- 1 file changed, 352 insertions(+), 226 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 141b963d59..795c8590d3 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -9,7 +9,7 @@ from logging import Logger from math import ceil from tempfile import TemporaryDirectory -from typing import Dict, List, Optional, Sequence, Tuple, TypeVar, Union, cast +from typing import Dict, List, Sequence, Tuple, TypeVar, Union, cast import cv2 import datumaro as dm @@ -89,6 +89,10 @@ class InvalidImageInfo(DatasetValidationError): pass +class InvalidCoordinates(DatasetValidationError): + pass + + T = TypeVar("T") @@ -102,6 +106,30 @@ def __bool__(self) -> bool: _MaybeUnset = Union[T, _Undefined] +@dataclass +class _ExcludedAnnotationInfo: + message: str + sample_id: str = field(kw_only=True) + sample_subset: str = field(kw_only=True) + + +@dataclass +class _ExcludedAnnotationsInfo: + errors: List[_ExcludedAnnotationInfo] = field(default_factory=list) + + excluded_count: int = 0 + "The number of excluded annotations. Can be different from len(error_messages)" + + total_count: int = 0 + + def add_error(self, message: str, *, sample_id: str, sample_subset: str): + self.errors.append( + _ExcludedAnnotationInfo( + message=message, sample_id=sample_id, sample_subset=sample_subset + ) + ) + + class BoxesFromPointsTaskBuilder: def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.exit_stack = ExitStack() @@ -111,29 +139,29 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.logger: Logger = NullLogger() - self.input_gt_data: _MaybeUnset[bytes] = _unset - self.input_points_data: _MaybeUnset[bytes] = _unset - - # Computed values - self.input_filenames: _MaybeUnset[Sequence[str]] = _unset - self.input_gt_dataset: _MaybeUnset[dm.Dataset] = _unset - self.input_points_dataset: _MaybeUnset[dm.Dataset] = _unset + self._input_gt_data: _MaybeUnset[bytes] = _unset + self._input_points_data: _MaybeUnset[bytes] = _unset - self.gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self._data_filenames: _MaybeUnset[Sequence[str]] = _unset + self._gt_dataset: _MaybeUnset[dm.Dataset] = _unset + self._points_dataset: _MaybeUnset[dm.Dataset] = _unset - self.bbox_point_mapping: _MaybeUnset[boxes_from_points_task.BboxPointMapping] = _unset + self._bbox_point_mapping: _MaybeUnset[boxes_from_points_task.BboxPointMapping] = _unset "bbox_id -> point_id" - self.roi_size_estimations: _MaybeUnset[Dict[int, Tuple[float, float]]] = _unset + self._roi_size_estimations: _MaybeUnset[Dict[int, Tuple[float, float]]] = _unset "label_id -> (rel. w, rel. h)" - self.rois: _MaybeUnset[boxes_from_points_task.RoiInfos] = _unset - self.roi_filenames: _MaybeUnset[boxes_from_points_task.RoiFilenames] = _unset + self._rois: _MaybeUnset[boxes_from_points_task.RoiInfos] = _unset + self._roi_filenames: _MaybeUnset[boxes_from_points_task.RoiFilenames] = _unset - self.job_layout: _MaybeUnset[Sequence[Sequence[str]]] = _unset + self._job_layout: _MaybeUnset[Sequence[Sequence[str]]] = _unset "File lists per CVAT job" - self.label_configuration: _MaybeUnset[Sequence[dict]] = _unset + self._label_configuration: _MaybeUnset[Sequence[dict]] = _unset + + self._excluded_points_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset + self._excluded_gt_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset # Configuration / constants # TODO: consider WebP if produced files are too big @@ -163,7 +191,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.min_class_samples_for_roi_estimation = 50 - self.max_discarded_threshold = 0.5 + self.max_discarded_threshold = 0.05 """ The maximum allowed percent of discarded GT boxes, points, or samples for successful job launch @@ -196,14 +224,14 @@ def _download_input_data(self): data_bucket.url.bucket_name, prefix=data_bucket.url.path, ) - self.input_filenames = filter_image_files(data_filenames) + self._data_filenames = filter_image_files(data_filenames) - self.input_gt_data = gt_storage_client.download_file( + self._input_gt_data = gt_storage_client.download_file( gt_bucket.url.bucket_name, gt_bucket.url.path, ) - self.input_points_data = points_storage_client.download_file( + self._input_points_data = points_storage_client.download_file( points_bucket.url.bucket_name, points_bucket.url.path, ) @@ -218,24 +246,24 @@ def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm return dm.Dataset.import_from(annotation_filename, format=dataset_format) def _parse_gt(self): - assert self.input_gt_data is not _unset + assert self._input_gt_data is not _unset - self.input_gt_dataset = self._parse_dataset( - self.input_gt_data, + self._gt_dataset = self._parse_dataset( + self._input_gt_data, dataset_format=DM_GT_DATASET_FORMAT_MAPPING[self.manifest.annotation.type], ) def _parse_points(self): - assert self.input_points_data is not _unset + assert self._input_points_data is not _unset - self.input_points_dataset = self._parse_dataset( - self.input_points_data, dataset_format=self.points_format + self._points_dataset = self._parse_dataset( + self._input_points_data, dataset_format=self.points_format ) def _validate_gt_labels(self): gt_labels = set( label.name - for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] + for label in self._gt_dataset.categories()[dm.AnnotationType.label] if not label.parent ) manifest_labels = set(label.name for label in self.manifest.annotation.labels) @@ -246,15 +274,15 @@ def _validate_gt_labels(self): ) ) - self.input_gt_dataset.transform( + self._gt_dataset.transform( ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self.input_gt_dataset.init_cache() + self._gt_dataset.init_cache() def _validate_gt_filenames(self): - gt_filenames = set(s.id + s.media.ext for s in self.input_gt_dataset) + gt_filenames = set(s.id + s.media.ext for s in self._gt_dataset) - known_data_filenames = set(self.input_filenames) + known_data_filenames = set(self._data_filenames) matched_gt_filenames = gt_filenames.intersection(known_data_filenames) if len(gt_filenames) != len(matched_gt_filenames): @@ -272,13 +300,76 @@ def _validate_gt_filenames(self): f"at least {self.manifest.validation.val_size} required." ) + def _validate_gt_annotations(self): + label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + + excluded_gt_info = _ExcludedAnnotationsInfo() + excluded_samples = set() + for gt_sample in self._gt_dataset: + # Could fail on this as well + img_h, img_w = gt_sample.media_as(dm.Image).size + + sample_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] + valid_boxes = [] + for bbox in sample_boxes: + if (0 <= bbox.x < bbox.x + bbox.w < img_w) and ( + 0 <= bbox.y < bbox.y + bbox.h < img_h + ): + valid_boxes.append(bbox) + else: + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) - invalid coordinates. " + "The image will be skipped".format( + gt_sample.id, bbox.id, label_cat[bbox.label].name + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + valid_boxes = [] + break + + valid_boxes.append(bbox) + + excluded_gt_info.excluded_count += len(sample_boxes) - len(valid_boxes) + excluded_gt_info.total_count += len(sample_boxes) + + if len(valid_boxes) != len(sample_boxes): + if not valid_boxes: + excluded_samples.add((gt_sample.id, gt_sample.subset)) + else: + self._gt_dataset.put(gt_sample.wrap(annotations=valid_boxes)) + + for excluded_sample in excluded_samples: + self._gt_dataset.remove(*excluded_sample) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT boxes were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + + if ( + excluded_gt_info.excluded_count + > excluded_gt_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many GT boxes discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_gt_info.errors] + ) + ) + ) + + self._excluded_gt_info = excluded_gt_info + def _validate_gt(self): - assert self.input_filenames is not _unset - assert self.input_gt_dataset is not _unset + assert self._data_filenames is not _unset + assert self._gt_dataset is not _unset self._validate_gt_filenames() self._validate_gt_labels() - # TODO: add gt annotation validation, keep track of excluded annotations + self._validate_gt_annotations() def _format_list( self, items: Sequence[str], *, max_items: int = None, separator: str = ", " @@ -294,7 +385,7 @@ def _format_list( def _validate_points_categories(self): invalid_point_categories_messages = [] - points_dataset_categories = self.input_points_dataset.categories() + points_dataset_categories = self._points_dataset.categories() points_dataset_label_cat: dm.LabelCategories = points_dataset_categories[ dm.AnnotationType.label ] @@ -322,15 +413,15 @@ def _validate_points_categories(self): if manifest_labels != points_labels: raise DatasetValidationError("Point labels do not match job labels") - self.input_points_dataset.transform( + self._points_dataset.transform( ProjectLabels, dst_labels=[label.name for label in self.manifest.annotation.labels] ) - self.input_points_dataset.init_cache() + self._points_dataset.init_cache() def _validate_points_filenames(self): - points_filenames = set(sample.id + sample.media.ext for sample in self.input_points_dataset) + points_filenames = set(sample.id + sample.media.ext for sample in self._points_dataset) - known_data_filenames = set(self.input_filenames) + known_data_filenames = set(self._data_filenames) matched_points_filenames = points_filenames.intersection(known_data_filenames) if len(known_data_filenames) != len(matched_points_filenames): @@ -351,65 +442,94 @@ def _validate_points_filenames(self): ) def _validate_points_annotations(self): - label_cat: dm.LabelCategories = self.input_points_dataset.categories()[ - dm.AnnotationType.label - ] + def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): + if len(skeleton.elements) != 1: + raise DatasetValidationError( + "invalid points count ({}), expected 1".format( + len(skeleton.elements), + ) + ) + + point = skeleton.elements[0] + px, py = point.points[:2] + if not is_point_in_bbox(px, py, sample_bbox): + raise InvalidCoordinates("coordinates are outside image") + + label_cat: dm.LabelCategories = self._points_dataset.categories()[dm.AnnotationType.label] - excluded_samples = [] - for sample in self.input_points_dataset: + excluded_points_info = _ExcludedAnnotationsInfo() + excluded_samples = set() + for sample in self._points_dataset: # Could fail on this as well image_h, image_w = sample.image.size + sample_bbox = dm.Bbox(0, 0, w=image_w, h=image_h) - for skeleton in sample.annotations: - # Could fail on this as well - if not isinstance(skeleton, dm.Skeleton): - continue - - if len(skeleton.elements) != 1: - message = ( - "Sample '{}': skeleton #{} ({}) skipped - " - "invalid points count ({}), expected 1".format( - sample.id, - skeleton.id, - label_cat[skeleton.label].name, - len(skeleton.elements), - ) + sample_skeletons = [a for a in sample.annotations if isinstance(a, dm.Skeleton)] + valid_skeletons = [] + for skeleton in sample_skeletons: + # Here 1 skeleton describes 1 point + try: + _validate_skeleton(skeleton, sample_bbox=sample_bbox) + except InvalidCoordinates as error: + excluded_points_info.add_error( + "Sample '{}': point #{} ({}) - {}. " + "The image will be skipped".format( + sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=sample.id, + sample_subset=sample.subset, ) - excluded_samples.append(((sample.id, sample.subset), message)) + valid_skeletons = [] break + except DatasetValidationError as error: + excluded_points_info.add_error( + "Sample '{}': point #{} ({}) - {}".format( + sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=sample.id, + sample_subset=sample.subset, + ) - point = skeleton.elements[0] - px, py = point.points[:2] + valid_skeletons.append(skeleton) - if px < 0 or py < 0 or px > image_w or py > image_h: - message = ( - "Sample '{}': point #{} ({}) skipped - " - "coordinates are outside image".format( - sample.id, skeleton.id, label_cat[skeleton.label].name - ) - ) - excluded_samples.append(((sample.id, sample.subset), message)) + excluded_points_info.excluded_count += len(sample_skeletons) - len(valid_skeletons) + excluded_points_info.total_count += len(sample_skeletons) - if len(excluded_samples) > len(self.input_points_dataset) * self.max_discarded_threshold: - raise DatasetValidationError( - "Too many samples discarded, canceling job creation. Errors: {}".format( - self._format_list([message for _, message in excluded_samples]) - ) - ) + if len(valid_skeletons) != len(sample_skeletons): + if not valid_skeletons: + excluded_samples.add((sample.id, sample.subset)) + else: + self._points_dataset.put(sample.wrap(annotations=valid_skeletons)) - for excluded_sample, _ in excluded_samples: - self.input_points_dataset.remove(*excluded_sample) + for excluded_sample in excluded_samples: + self._points_dataset.remove(*excluded_sample) - if excluded_samples: + if excluded_points_info.excluded_count: self.logger.warning( - "Some samples were excluded due to errors found: {}".format( - self._format_list([m for _, m in excluded_samples], separator="\n") + "Some points were excluded due to the errors found: {}".format( + self._format_list( + [e.message for e in excluded_points_info.errors], separator="\n" + ) ) ) + if ( + excluded_points_info.excluded_count + > excluded_points_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many points discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_points_info.errors] + ) + ) + ) + + self._excluded_points_info = excluded_points_info + def _validate_points(self): - assert self.input_filenames is not _unset - assert self.input_points_dataset is not _unset + assert self._data_filenames is not _unset + assert self._points_dataset is not _unset self._validate_points_categories() self._validate_points_filenames() @@ -417,38 +537,32 @@ def _validate_points(self): @staticmethod def _is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: - return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) + return is_point_in_bbox(px, py, bbox) def _prepare_gt(self): - assert self.input_filenames is not _unset - assert self.input_points_dataset is not _unset - assert self.input_gt_dataset is not _unset - assert [ - label.name for label in self.input_gt_dataset.categories()[dm.AnnotationType.label] - ] == [label.name for label in self.manifest.annotation.labels] + assert self._data_filenames is not _unset + assert self._points_dataset is not _unset + assert self._gt_dataset is not _unset + assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ + label.name for label in self.manifest.annotation.labels + ] assert [ label.name - for label in self.input_points_dataset.categories()[dm.AnnotationType.label] + for label in self._points_dataset.categories()[dm.AnnotationType.label] if not label.parent ] == [label.name for label in self.manifest.annotation.labels] - gt_dataset = dm.Dataset(categories=self.input_gt_dataset.categories(), media_type=dm.Image) + gt_dataset = dm.Dataset(categories=self._gt_dataset.categories(), media_type=dm.Image) - gt_label_cat: dm.LabelCategories = self.input_gt_dataset.categories()[ - dm.AnnotationType.label - ] + gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] - excluded_boxes_messages = [] - total_boxes = 0 + excluded_gt_info = self._excluded_gt_info gt_count_per_class = {} - bbox_point_mapping = {} # bbox id -> point id - for gt_sample in self.input_gt_dataset: - points_sample = self.input_points_dataset.get(gt_sample.id, gt_sample.subset) + for gt_sample in self._gt_dataset: + points_sample = self._points_dataset.get(gt_sample.id, gt_sample.subset) assert points_sample - image_h, image_w = points_sample.image.size - gt_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] input_skeletons = [a for a in points_sample.annotations if isinstance(a, dm.Skeleton)] @@ -456,36 +570,22 @@ def _prepare_gt(self): if not gt_boxes: continue - total_boxes += len(gt_boxes) - matched_boxes = [] visited_skeletons = set() for gt_bbox in gt_boxes: gt_bbox_id = gt_bbox.id - if ( - gt_bbox.x < 0 - or gt_bbox.y < 0 - or gt_bbox.x + gt_bbox.w > image_w - or gt_bbox.y + gt_bbox.h > image_h - ): - excluded_boxes_messages.append( - "Sample '{}': GT bbox #{} ({}) - " - "coordinates are outside image. The image will be skipped".format( - gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name - ) - ) - matched_boxes = [] - break - if len(visited_skeletons) == len(gt_boxes): # Handle unmatched boxes - excluded_boxes_messages.append( + excluded_gt_info.add_error( "Sample '{}': GT bbox #{} ({}) skipped - " "no matching points found".format( gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name - ) + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, ) + excluded_gt_info.excluded_count += 1 continue matched_skeletons: List[dm.Skeleton] = [] @@ -506,26 +606,32 @@ def _prepare_gt(self): if len(matched_skeletons) > 1: # Handle ambiguous matches - excluded_boxes_messages.append( + excluded_gt_info.add_error( "Sample '{}': GT bbox #{} ({}) skipped - " "too many matching points ({}) found".format( gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name, len(matched_skeletons), - ) + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, ) + excluded_gt_info.excluded_count += 1 continue elif len(matched_skeletons) == 0: # Handle unmatched boxes - excluded_boxes_messages.append( + excluded_gt_info.add_error( "Sample '{}': GT bbox #{} ({}) skipped - " "no matching points found".format( gt_sample.id, gt_bbox_id, gt_label_cat[gt_bbox.label].name, - ) + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, ) + excluded_gt_info.excluded_count += 1 continue gt_count_per_class[gt_bbox.label] = gt_count_per_class.get(gt_bbox.label, 0) + 1 @@ -538,15 +644,24 @@ def _prepare_gt(self): gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) - if len(bbox_point_mapping) < (1 - self.max_discarded_threshold) * total_boxes: + if ( + excluded_gt_info.excluded_count + > excluded_gt_info.total_count * self.max_discarded_threshold + ): raise DatasetValidationError( "Too many GT boxes discarded ({} out of {}). " "Please make sure each GT box matches exactly 1 point".format( - total_boxes - len(bbox_point_mapping), total_boxes + excluded_gt_info.total_count - len(bbox_point_mapping), + excluded_gt_info.total_count, + ) + ) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT annotations were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") ) ) - elif excluded_boxes_messages: - self.logger.warning(self._format_list(excluded_boxes_messages, separator="\n")) gt_labels_without_anns = [ gt_label_cat[label_id] @@ -560,18 +675,18 @@ def _prepare_gt(self): ) ) - self.gt_dataset = gt_dataset - self.bbox_point_mapping = bbox_point_mapping + self._gt_dataset = gt_dataset + self._bbox_point_mapping = bbox_point_mapping def _estimate_roi_sizes(self): - assert self.gt_dataset is not _unset - assert [label.name for label in self.gt_dataset.categories()[dm.AnnotationType.label]] == [ + assert self._gt_dataset is not _unset + assert [label.name for label in self._gt_dataset.categories()[dm.AnnotationType.label]] == [ label.name for label in self.manifest.annotation.labels ] bbox_sizes_per_label = {} - for sample in self.gt_dataset: - image_h, image_w = self.input_points_dataset.get(sample.id, sample.subset).image.size + for sample in self._gt_dataset: + image_h, image_w = self._points_dataset.get(sample.id, sample.subset).image.size for gt_bbox in sample.annotations: gt_bbox = cast(dm.Bbox, gt_bbox) @@ -599,7 +714,7 @@ def _estimate_roi_sizes(self): roi_size_estimations_per_label[label_id] = estimated_size if classes_with_default_roi: - label_cat = self.gt_dataset.categories()[dm.AnnotationType.label] + label_cat = self._gt_dataset.categories()[dm.AnnotationType.label] self.logger.warning( "Some classes will use the full image instead of RoI" "- too few GT provided: {}".format( @@ -609,15 +724,15 @@ def _estimate_roi_sizes(self): ) ) - self.roi_size_estimations = roi_size_estimations_per_label + self._roi_size_estimations = roi_size_estimations_per_label def _prepare_roi_info(self): - assert self.gt_dataset is not _unset - assert self.roi_size_estimations is not _unset - assert self.input_points_dataset is not _unset + assert self._gt_dataset is not _unset + assert self._roi_size_estimations is not _unset + assert self._points_dataset is not _unset rois: List[boxes_from_points_task.RoiInfo] = [] - for sample in self.input_points_dataset: + for sample in self._points_dataset: for skeleton in sample.annotations: if not isinstance(skeleton, dm.Skeleton): continue @@ -629,7 +744,7 @@ def _prepare_roi_info(self): image_h, image_w = sample.image.size - roi_est_w, roi_est_h = self.roi_size_estimations[point_label_id] + roi_est_w, roi_est_h = self._roi_size_estimations[point_label_id] roi_est_w *= image_w roi_est_h *= image_h @@ -657,33 +772,33 @@ def _prepare_roi_info(self): ) ) - self.rois = rois + self._rois = rois def _mangle_filenames(self): """ Mangle filenames in the dataset to make them less recognizable by annotators and hide private dataset info """ - assert self.rois is not _unset + assert self._rois is not _unset # TODO: maybe add different names for the same GT images in # different jobs to make them even less recognizable - self.roi_filenames = { - roi.point_id: str(uuid.uuid4()) + self.roi_file_ext for roi in self.rois + self._roi_filenames = { + roi.point_id: str(uuid.uuid4()) + self.roi_file_ext for roi in self._rois } def _prepare_job_layout(self): # Make job layouts wrt. manifest params # 1 job per task as CVAT can't repeat images in jobs, but GTs can repeat in the dataset - assert self.rois is not _unset - assert self.bbox_point_mapping is not _unset + assert self._rois is not _unset + assert self._bbox_point_mapping is not _unset - gt_point_ids = set(self.bbox_point_mapping.values()) - gt_filenames = [self.roi_filenames[point_id] for point_id in gt_point_ids] + gt_point_ids = set(self._bbox_point_mapping.values()) + gt_filenames = [self._roi_filenames[point_id] for point_id in gt_point_ids] data_filenames = [ - fn for point_id, fn in self.roi_filenames.items() if not point_id in gt_point_ids + fn for point_id, fn in self._roi_filenames.items() if not point_id in gt_point_ids ] random.shuffle(data_filenames) @@ -694,34 +809,32 @@ def _prepare_job_layout(self): random.shuffle(job_samples) job_layout.append(job_samples) - self.job_layout = job_layout + self._job_layout = job_layout def _prepare_label_configuration(self): - self.label_configuration = make_label_configuration(self.manifest) + self._label_configuration = make_label_configuration(self.manifest) def _upload_task_meta(self): layout = boxes_from_points_task.TaskMetaLayout() serializer = boxes_from_points_task.TaskMetaSerializer() file_list = [] - file_list.append( - (self.input_points_data, layout.POINTS_FILENAME) - ) # TODO: save cleaned version as well + file_list.append((self._input_points_data, layout.POINTS_FILENAME)) file_list.append( ( - serializer.serialize_gt_annotations(self.gt_dataset), + serializer.serialize_gt_annotations(self._gt_dataset), layout.GT_FILENAME, ) ) file_list.append( ( - serializer.serialize_bbox_point_mapping(self.bbox_point_mapping), + serializer.serialize_bbox_point_mapping(self._bbox_point_mapping), layout.BBOX_POINT_MAPPING_FILENAME, ) ) - file_list.append((serializer.serialize_roi_info(self.rois), layout.ROI_INFO_FILENAME)) + file_list.append((serializer.serialize_roi_info(self._rois), layout.ROI_INFO_FILENAME)) file_list.append( - (serializer.serialize_roi_filenames(self.roi_filenames), layout.ROI_FILENAMES_FILENAME) + (serializer.serialize_roi_filenames(self._roi_filenames), layout.ROI_FILENAMES_FILENAME) ) storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) @@ -768,10 +881,10 @@ def _extract_and_upload_rois(self): # TODO: maybe optimize via splitting into separate threads (downloading, uploading, processing) # Watch for the memory used, as the whole dataset can be quite big (gigabytes, terabytes) # Consider also packing RoIs cut into archives - assert self.input_points_dataset is not _unset - assert self.rois is not _unset - assert self.input_filenames is not _unset - assert self.roi_filenames is not _unset + assert self._points_dataset is not _unset + assert self._rois is not _unset + assert self._data_filenames is not _unset + assert self._roi_filenames is not _unset src_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) src_prefix = "" @@ -781,18 +894,18 @@ def _extract_and_upload_rois(self): dst_client = self._make_cloud_storage_client(dst_bucket) image_id_to_filename = { - sample.attributes["id"]: sample.image.path for sample in self.input_points_dataset + sample.attributes["id"]: sample.image.path for sample in self._points_dataset } - filename_to_sample = {sample.image.path: sample for sample in self.input_points_dataset} + filename_to_sample = {sample.image.path: sample for sample in self._points_dataset} _roi_key = lambda e: e.original_image_key rois_by_image: Dict[str, Sequence[boxes_from_points_task.RoiInfo]] = { image_id_to_filename[image_id]: list(g) - for image_id, g in groupby(sorted(self.rois, key=_roi_key), key=_roi_key) + for image_id, g in groupby(sorted(self._rois, key=_roi_key), key=_roi_key) } - for filename in self.input_filenames: + for filename in self._data_filenames: image_roi_infos = rois_by_image.get(filename, []) if not image_roi_infos: continue @@ -821,7 +934,7 @@ def _extract_and_upload_rois(self): if self.embed_point_in_roi_image: roi_pixels = self._draw_roi_point(roi_pixels, roi_info) - roi_filename = self.roi_filenames[roi_info.point_id] + roi_filename = self._roi_filenames[roi_info.point_id] roi_bytes = encode_image(roi_pixels, os.path.splitext(roi_filename)[-1]) image_rois[roi_filename] = roi_bytes @@ -834,8 +947,8 @@ def _extract_and_upload_rois(self): ) def _create_on_cvat(self): - assert self.job_layout is not _unset - assert self.label_configuration is not _unset + assert self._job_layout is not _unset + assert self._label_configuration is not _unset input_data_bucket = BucketAccessInfo.from_raw_url(self.manifest.data.data_url) oracle_bucket = self.oracle_data_bucket @@ -852,7 +965,7 @@ def _create_on_cvat(self): # Create a project project = cvat_api.create_project( self.escrow_address, - labels=self.label_configuration, + labels=self._label_configuration, user_guide=self.manifest.annotation.user_guide, ) @@ -880,11 +993,11 @@ def _create_on_cvat(self): project.id, [ compose_data_bucket_filename(self.escrow_address, self.chain_id, fn) - for fn in self.roi_filenames.values() + for fn in self._roi_filenames.values() ], ) - for job_filenames in self.job_layout: + for job_filenames in self._job_layout: task = cvat_api.create_task(project.id, self.escrow_address) with SessionLocal.begin() as session: @@ -932,28 +1045,6 @@ def build(self): class SkeletonsFromBoxesTaskBuilder: - @dataclass - class _ExcludedAnnotationsInfo: - @dataclass - class _ExcludedAnnotationInfo: - message: str - sample_id: str = field(kw_only=True) - sample_subset: str = field(kw_only=True) - - errors: List[_ExcludedAnnotationInfo] = field(default_factory=list) - - excluded_count: int = 0 - "The number of excluded annotations. Can be different from len(error_messages)" - - total_count: int = 0 - - def add_error(self, message: str, *, sample_id: str, sample_subset: str): - self.errors.append( - self._ExcludedAnnotationInfo( - message=message, sample_id=sample_id, sample_subset=sample_subset - ) - ) - @dataclass class _JobParams: label_id: int @@ -981,8 +1072,8 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self._roi_filenames: _MaybeUnset[Dict[int, str]] = _unset self._job_params: _MaybeUnset[List[self._JobParams]] = _unset - self._excluded_gt_info: _MaybeUnset[self._ExcludedAnnotationsInfo] = _unset - self._excluded_boxes_info: _MaybeUnset[self._ExcludedAnnotationsInfo] = _unset + self._excluded_gt_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset + self._excluded_boxes_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset # Configuration / constants self.job_size_mult = 6 @@ -1015,7 +1106,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.min_label_gt_samples = 2 # TODO: find good threshold - self.max_discarded_threshold = 0.5 + self.max_discarded_threshold = 0.05 """ The maximum allowed percent of discarded GT annotations or samples for successful job launch @@ -1136,10 +1227,9 @@ def _validate_gt_filenames(self): ) def _validate_gt_annotations(self): - def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox) -> Optional[str]: + def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): for element in skeleton.elements: - # This is what Datumaro expected to parse - assert isinstance(element, dm.Points) + # This is what Datumaro is expected to parse assert len(element.points) == 2 and len(element.visibility) == 1 if element.visibility[0] != dm.Points.Visibility.visible: @@ -1147,12 +1237,12 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox) -> Option px, py = element.points[:2] if not is_point_in_bbox(px, py, sample_bbox): - raise DatasetValidationError("skeleton point is outside the image") - - excluded_gt_info = self._ExcludedAnnotationsInfo() + raise InvalidCoordinates("skeleton point is outside the image") label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + excluded_gt_info = _ExcludedAnnotationsInfo() + excluded_samples = set() for gt_sample in self._gt_dataset: # Could fail on this as well img_h, img_w = gt_sample.media_as(dm.Image).size @@ -1163,26 +1253,64 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox) -> Option for skeleton in sample_skeletons: try: _validate_skeleton(skeleton, sample_bbox=sample_bbox) + except InvalidCoordinates as error: + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) - {}. " + "The image will be skipped".format( + gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + valid_skeletons = [] + break except DatasetValidationError as error: excluded_gt_info.add_error( - "Sample '{}': skeleton #{} ({}) skipped - {}".format( + "Sample '{}': GT skeleton #{} ({}) skipped - {}".format( gt_sample.id, skeleton.id, label_cat[skeleton.label].name, error ), sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) + valid_skeletons.append(skeleton) + excluded_gt_info.excluded_count += len(sample_skeletons) - len(valid_skeletons) excluded_gt_info.total_count += len(sample_skeletons) if len(valid_skeletons) != len(sample_skeletons): - # Skeleton boxes can be in the list as well with the same ids / groups - skeleton_ids = set(a.id for a in valid_skeletons) - {0} - self._gt_dataset.put( - gt_sample.wrap( - annotations=[a for a in gt_sample.annotations if a.id in skeleton_ids] + if not valid_skeletons: + excluded_samples.add((gt_sample.id, gt_sample.subset)) + else: + # Skeleton boxes can be in the list as well with the same ids / groups + skeleton_ids = set(a.id for a in valid_skeletons) - {0} + self._gt_dataset.put( + gt_sample.wrap( + annotations=[a for a in gt_sample.annotations if a.id in skeleton_ids] + ) + ) + + for excluded_sample in excluded_samples: + self._gt_dataset.remove(*excluded_sample) + + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT skeletons were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + + if ( + excluded_gt_info.excluded_count + > excluded_gt_info.total_count * self.max_discarded_threshold + ): + raise TooFewSamples( + "Too many GT skeletons discarded, canceling job creation. Errors: {}".format( + self._format_list( + [error_info.message for error_info in excluded_gt_info.errors] ) ) + ) self._excluded_gt_info = excluded_gt_info @@ -1235,7 +1363,7 @@ def _validate_boxes_filenames(self): ) def _validate_boxes_annotations(self): - excluded_boxes_info = self._ExcludedAnnotationsInfo() + excluded_boxes_info = _ExcludedAnnotationsInfo() label_cat: dm.LabelCategories = self._boxes_dataset.categories()[dm.AnnotationType.label] @@ -1284,7 +1412,7 @@ def _validate_boxes_annotations(self): if excluded_samples: self.logger.warning( - "Some boxes were excluded due to errors found: {}".format( + "Some boxes were excluded due to the errors found: {}".format( self._format_list( [e.message for e in excluded_boxes_info.errors], separator="\n" ) @@ -1337,6 +1465,7 @@ def _prepare_gt(self): gt_label_cat: dm.LabelCategories = self._gt_dataset.categories()[dm.AnnotationType.label] + excluded_gt_info = self._excluded_gt_info gt_count_per_class = {} skeleton_bbox_mapping = {} # skeleton id -> bbox id for gt_sample in self._gt_dataset: @@ -1360,7 +1489,7 @@ def _prepare_gt(self): if len(visited_skeletons) == len(gt_skeletons): # Handle unmatched boxes - self._excluded_gt_info.add_error( + excluded_gt_info.add_error( "Sample '{}': GT skeleton #{} ({}) skipped - " "no matching boxes found".format( gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name @@ -1368,7 +1497,7 @@ def _prepare_gt(self): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - self._excluded_gt_info.excluded_count += 1 + excluded_gt_info.excluded_count += 1 continue matched_boxes: List[dm.Bbox] = [] @@ -1389,7 +1518,7 @@ def _prepare_gt(self): if len(matched_boxes) > 1: # Handle ambiguous matches - self._excluded_gt_info.add_error( + excluded_gt_info.add_error( "Sample '{}': GT skeleton #{} ({}) skipped - " "too many matching boxes ({}) found".format( gt_sample.id, @@ -1400,11 +1529,11 @@ def _prepare_gt(self): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - self._excluded_gt_info.excluded_count += 1 + excluded_gt_info.excluded_count += 1 continue elif len(matched_boxes) == 0: # Handle unmatched boxes - self._excluded_gt_info.add_error( + excluded_gt_info.add_error( "Sample '{}': GT skeleton #{} ({}) skipped - " "no matching boxes found".format( gt_sample.id, @@ -1414,7 +1543,7 @@ def _prepare_gt(self): sample_id=gt_sample.id, sample_subset=gt_sample.subset, ) - self._excluded_gt_info.excluded_count += 1 + excluded_gt_info.excluded_count += 1 continue # TODO: maybe check if the top match is good enough @@ -1434,20 +1563,20 @@ def _prepare_gt(self): if ( len(skeleton_bbox_mapping) - < (1 - self.max_discarded_threshold) * self._excluded_gt_info.total_count + < (1 - self.max_discarded_threshold) * excluded_gt_info.total_count ): raise DatasetValidationError( "Too many GT skeletons discarded ({} out of {}). " "Please make sure each GT skeleton matches exactly 1 bbox".format( - self._excluded_gt_info.total_count - len(skeleton_bbox_mapping), - self._excluded_gt_info.total_count, + excluded_gt_info.total_count - len(skeleton_bbox_mapping), + excluded_gt_info.total_count, ) ) - if self._excluded_gt_info.errors: + if excluded_gt_info.excluded_count: self.logger.warning( - "Some GT annotations were excluded due to errors found: {}".format( - self._format_list(self._excluded_gt_info.errors, separator="\n") + "Some GT annotations were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") ) ) @@ -1574,9 +1703,6 @@ def _prepare_job_labels(self): self.point_labels[(skeleton_label.name, point_name)] = point_name def _upload_task_meta(self): - # TODO: maybe extract into a separate function / class / library, - # extract constants, serialization methods return TaskConfig from build() - layout = skeletons_from_boxes_task.TaskMetaLayout() serializer = skeletons_from_boxes_task.TaskMetaSerializer() From b2b2f935186757dc07ce444c00ca07f62796cff8 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 13 Feb 2024 15:03:46 +0200 Subject: [PATCH 44/82] Fix quality computation, refactor --- .../src/validation/dataset_comparison.py | 208 +++++++++--------- 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index a1ac3c117b..1ae6895ea8 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -1,10 +1,13 @@ +from __future__ import annotations + import itertools from abc import ABCMeta, abstractmethod -from typing import Any, Callable, Dict, Optional, Sequence, Tuple +from typing import Callable, Dict, Optional, Sequence, Tuple, Union import datumaro as dm import numpy as np from attrs import define +from datumaro.util.annotation_util import BboxCoords from .annotation_matching import ( Bbox, @@ -16,14 +19,21 @@ ) -class CachedSimilarityFunction: +class SimilarityFunction(metaclass=ABCMeta): + "A function to compute similarity between 2 annotations" + + def __call__(self, gt_ann: dm.Annotation, ds_ann: dm.Annotation) -> float: + ... + + +class CachedSimilarityFunction(SimilarityFunction): def __init__( self, sim_fn: Callable, *, cache: Optional[Dict[Tuple[int, int], float]] = None ) -> None: self.cache: Dict[Tuple[int, int], float] = cache or {} self.sim_fn = sim_fn - def __call__(self, gt_ann: Any, ds_ann: Any) -> float: + def __call__(self, gt_ann: dm.Annotation, ds_ann: dm.Annotation) -> float: key = ( id(gt_ann), id(ds_ann), @@ -44,16 +54,9 @@ def clear_cache(self): class DatasetComparator(metaclass=ABCMeta): min_similarity_threshold: float - @abstractmethod def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - ... - - -class BboxDatasetComparator(DatasetComparator): - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - similarity_fn = CachedSimilarityFunction(bbox_iou) - all_similarities = [] + total_anns_to_compare = 0 for ds_sample in ds_dataset: gt_sample = gt_dataset.get(ds_sample.id) @@ -61,87 +64,97 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: if not gt_sample: continue - ds_boxes = [ - Bbox(a.x, a.y, a.w, a.h, a.label) - for a in ds_sample.annotations - if isinstance(a, dm.Bbox) - ] - gt_boxes = [ - Bbox(a.x, a.y, a.w, a.h, a.label) - for a in gt_sample.annotations - if isinstance(a, dm.Bbox) - ] - - matching_result = match_annotations( - gt_boxes, - ds_boxes, - similarity=similarity_fn, - min_similarity=self.min_similarity_threshold, - ) + matching_result, similarity_fn = self.compare_sample_annotations(gt_sample, ds_sample) - for gt_bbox, ds_bbox in itertools.chain( + for gt_ann, ds_ann in itertools.chain( matching_result.matches, matching_result.mispred, zip(matching_result.a_extra, itertools.repeat(None)), zip(itertools.repeat(None), matching_result.b_extra), ): - sim = similarity_fn(gt_bbox, ds_bbox) if gt_bbox and ds_bbox else 0 + sim = similarity_fn(gt_ann, ds_ann) if gt_ann and ds_ann else 0 all_similarities.append(sim) - return np.mean(all_similarities) if all_similarities else 0 + total_anns_to_compare += (gt_ann is not None) + (ds_ann is not None) + accuracy = 0 + if total_anns_to_compare: + accuracy = 2 * np.sum(all_similarities) / total_anns_to_compare -class PointsDatasetComparator(DatasetComparator): - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - similarity_fn = CachedSimilarityFunction(point_to_bbox_cmp) + return accuracy - all_similarities = [] + @abstractmethod + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + ) -> Tuple[MatchResult, SimilarityFunction]: + ... - for ds_sample in ds_dataset: - gt_sample = gt_dataset.get(ds_sample.id) - if not gt_sample: - continue +class BboxDatasetComparator(DatasetComparator): + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + ) -> Tuple[MatchResult, SimilarityFunction]: + similarity_fn = CachedSimilarityFunction(bbox_iou) - ds_points = [ - Point( - a.elements[0].points[0], - a.elements[0].points[1], - a.elements[0].label, - ) - for a in ds_sample.annotations - if isinstance(a, dm.Skeleton) - ] - gt_boxes = [ - Bbox(a.x, a.y, a.w, a.h, a.label) - for a in gt_sample.annotations - if isinstance(a, dm.Bbox) - ] - - matching_result = match_annotations( - gt_boxes, - ds_points, - similarity=similarity_fn, - min_similarity=self.min_similarity_threshold, + ds_boxes = [ + Bbox(a.x, a.y, a.w, a.h, a.label) + for a in ds_sample.annotations + if isinstance(a, dm.Bbox) + ] + gt_boxes = [ + Bbox(a.x, a.y, a.w, a.h, a.label) + for a in gt_sample.annotations + if isinstance(a, dm.Bbox) + ] + + matching_result = match_annotations( + gt_boxes, + ds_boxes, + similarity=similarity_fn, + min_similarity=self.min_similarity_threshold, + ) + + return matching_result, similarity_fn + + +class PointsDatasetComparator(DatasetComparator): + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + ) -> Tuple[MatchResult, SimilarityFunction]: + similarity_fn = CachedSimilarityFunction(point_to_bbox_cmp) + + ds_points = [ + Point( + a.elements[0].points[0], + a.elements[0].points[1], + a.elements[0].label, ) + for a in ds_sample.annotations + if isinstance(a, dm.Skeleton) + ] + gt_boxes = [ + Bbox(a.x, a.y, a.w, a.h, a.label) + for a in gt_sample.annotations + if isinstance(a, dm.Bbox) + ] - for gt_bbox, ds_point in itertools.chain( - matching_result.matches, - matching_result.mispred, - zip(matching_result.a_extra, itertools.repeat(None)), - zip(itertools.repeat(None), matching_result.b_extra), - ): - sim = similarity_fn(gt_bbox, ds_point) if gt_bbox and ds_point else 0 - all_similarities.append(sim) + matching_result = match_annotations( + gt_boxes, + ds_points, + similarity=similarity_fn, + min_similarity=self.min_similarity_threshold, + ) - return np.mean(all_similarities) if all_similarities else 0 + return matching_result, similarity_fn class SkeletonDatasetComparator(DatasetComparator): + _SkeletonInfo = list[str] + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._skeleton_info: Dict[int, list[str]] = {} + self._skeleton_info: Dict[int, self._SkeletonInfo] = {} self.categories: Optional[dm.CategoriesInfo] = None # TODO: find better strategy for sigma estimation @@ -149,36 +162,14 @@ def __init__(self, *args, **kwargs): def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: self.categories = gt_dataset.categories() + return super().compare(gt_dataset, ds_dataset) - all_similarities = [] - total_anns_to_compare = 0 - - for ds_sample in ds_dataset: - gt_sample = gt_dataset.get(ds_sample.id) - - if not gt_sample: - continue - - matching_result, similarity_fn = self.match_skeletons(gt_sample, ds_sample) - - for gt_skeleton, ds_skeleton in itertools.chain( - matching_result.matches, - matching_result.mispred, - zip(matching_result.a_extra, itertools.repeat(None)), - zip(itertools.repeat(None), matching_result.b_extra), - ): - sim = similarity_fn(gt_skeleton, ds_skeleton) if gt_skeleton and ds_skeleton else 0 - all_similarities.append(sim) - - total_anns_to_compare += (gt_skeleton is not None) + (ds_skeleton is not None) - - accuracy = 0 - if total_anns_to_compare: - accuracy = 2 * np.sum(all_similarities) / total_anns_to_compare - - return accuracy + def compare_sample_annotations( + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + ) -> Tuple[MatchResult, SimilarityFunction]: + return self._match_skeletons(gt_sample, ds_sample) - def _get_skeleton_info(self, skeleton_label_id: int): + def _get_skeleton_info(self, skeleton_label_id: int) -> _SkeletonInfo: label_cat: dm.LabelCategories = self.categories[dm.AnnotationType.label] skeleton_info = self._skeleton_info.get(skeleton_label_id) @@ -193,7 +184,9 @@ def _get_skeleton_info(self, skeleton_label_id: int): return skeleton_info - def match_skeletons(self, item_a, item_b): + def _match_skeletons( + self, item_a: dm.DatasetItem, item_b: dm.DatasetItem + ) -> Tuple[MatchResult, SimilarityFunction]: a_skeletons = [a for a in item_a.annotations if isinstance(a, dm.Skeleton)] b_skeletons = [a for a in item_b.annotations if isinstance(a, dm.Skeleton)] @@ -292,7 +285,7 @@ def distance(self, a: dm.Points, b: dm.Points) -> float: return 0 bbox = dm.ops.mean_bbox([a_bbox, b_bbox]) - return self._OKS( + return self._compute_oks( a, b, sigma=self.sigma, @@ -302,11 +295,19 @@ def distance(self, a: dm.Points, b: dm.Points) -> float: ) @classmethod - def _OKS( - cls, a, b, sigma=0.1, bbox=None, scale=None, visibility_a=None, visibility_b=None + def _compute_oks( + cls, + a: dm.Points, + b: dm.Points, + *, + sigma: Union[float, np.ndarray] = 0.1, + bbox: Optional[BboxCoords] = None, + scale: Union[None, float, np.ndarray] = None, + visibility_a: Union[None, bool, np.ndarray] = None, + visibility_b: Union[None, bool, np.ndarray] = None, ) -> float: """ - Object Keypoint Similarity metric. + Computes Object Keypoint Similarity metric for a pair of point sets. https://cocodataset.org/#keypoints-eval """ @@ -332,6 +333,9 @@ def _OKS( total_vis = np.sum(visibility_a | visibility_b, dtype=float) if not total_vis: + # We treat this situation as match. It's possible to use alternative approaches, + # such as add weight for occluded points. Our current annotation approach + # doesn't allow to distinguish between 'occluded' and 'absent' points. return 1.0 dists = np.linalg.norm(p1 - p2, axis=1) From 0b2f5570b7e2ea431f0245ca5719c0478852fa5f Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 20 Feb 2024 15:12:48 +0200 Subject: [PATCH 45/82] Unify assignment accuracy checks between different job types --- .../src/validation/dataset_comparison.py | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 1ae6895ea8..c04b4e2939 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -2,7 +2,7 @@ import itertools from abc import ABCMeta, abstractmethod -from typing import Callable, Dict, Optional, Sequence, Tuple, Union +from typing import Callable, Dict, Optional, Sequence, Set, Tuple, Union import datumaro as dm import numpy as np @@ -54,9 +54,9 @@ def clear_cache(self): class DatasetComparator(metaclass=ABCMeta): min_similarity_threshold: float - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - all_similarities = [] - total_anns_to_compare = 0 + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> Tuple[float, Set[str]]: + dataset_similarities = [] + dataset_total_anns_to_compare = 0 for ds_sample in ds_dataset: gt_sample = gt_dataset.get(ds_sample.id) @@ -66,6 +66,8 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: matching_result, similarity_fn = self.compare_sample_annotations(gt_sample, ds_sample) + sample_similarities = [] + sample_total_anns_to_compare = [] for gt_ann, ds_ann in itertools.chain( matching_result.matches, matching_result.mispred, @@ -73,15 +75,23 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: zip(itertools.repeat(None), matching_result.b_extra), ): sim = similarity_fn(gt_ann, ds_ann) if gt_ann and ds_ann else 0 + sample_total_anns_to_compare += (gt_ann is not None) + (ds_ann is not None) + + dataset_similarities.extend(sample_similarities) + dataset_total_anns_to_compare += sample_total_anns_to_compare + + sample_accuracy = 0 + if sample_total_anns_to_compare: + sample_accuracy = 2 * np.sum(sample_similarities) / sample_total_anns_to_compare all_similarities.append(sim) total_anns_to_compare += (gt_ann is not None) + (ds_ann is not None) - accuracy = 0 - if total_anns_to_compare: - accuracy = 2 * np.sum(all_similarities) / total_anns_to_compare + dataset_accuracy = 0 + if dataset_total_anns_to_compare: + dataset_accuracy = 2 * np.sum(dataset_similarities) / dataset_total_anns_to_compare - return accuracy + return dataset_accuracy @abstractmethod def compare_sample_annotations( @@ -155,13 +165,13 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._skeleton_info: Dict[int, self._SkeletonInfo] = {} - self.categories: Optional[dm.CategoriesInfo] = None + self._categories: Optional[dm.CategoriesInfo] = None # TODO: find better strategy for sigma estimation self.oks_sigma = 0.1 # average value for COCO points - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: - self.categories = gt_dataset.categories() + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> Tuple[float, Set[str]]: + self._categories = gt_dataset.categories() return super().compare(gt_dataset, ds_dataset) def compare_sample_annotations( @@ -170,7 +180,7 @@ def compare_sample_annotations( return self._match_skeletons(gt_sample, ds_sample) def _get_skeleton_info(self, skeleton_label_id: int) -> _SkeletonInfo: - label_cat: dm.LabelCategories = self.categories[dm.AnnotationType.label] + label_cat: dm.LabelCategories = self._categories[dm.AnnotationType.label] skeleton_info = self._skeleton_info.get(skeleton_label_id) if skeleton_info is None: From de09cbf95501dbaac74ad820f7825ca770b27070 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 20 Feb 2024 18:13:56 +0200 Subject: [PATCH 46/82] Refactor GT downloading for validation --- .../handlers/process_intermediate_results.py | 238 +++++++++++------- .../src/handlers/validation.py | 9 - 2 files changed, 143 insertions(+), 104 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 330e84f68f..32bef2d16b 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -3,7 +3,7 @@ import os from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Optional, Sequence, Tuple, Type, Union +from typing import Dict, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union import datumaro as dm import numpy as np @@ -71,6 +71,8 @@ class ValidationFailure: _JobResults = Dict[int, float] _RejectedJobs = Sequence[int] +T = TypeVar("T") + class _TaskValidator: def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): @@ -78,74 +80,151 @@ def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): self.chain_id = chain_id self.manifest = manifest - self.input_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] - - self.gt_annotations: Optional[io.IOBase] = None self.job_annotations: Optional[Dict[int, io.IOBase]] = None self.merged_annotations: Optional[io.IOBase] = None - def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: - assert self.gt_annotations is not None - assert self.job_annotations is not None - assert self.merged_annotations is not None + self._temp_dir: Optional[Path] = None + self._gt_dataset: Optional[dm.Dataset] = None - manifest = self.manifest - task_type = manifest.annotation.type - dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + def _require_field(self, field: Optional[T]) -> T: + assert field is not None + return field - gt_annotations = self.gt_annotations - job_annotations = self.job_annotations - merged_annotations = self.merged_annotations + + def _parse_gt(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + + bucket_info = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) + bucket_client = make_cloud_client(bucket_info.url) + # TODO: add credentials + + gt_annotations = io.BytesIO( + bucket_client.download_file(bucket_info.url.bucket_name, bucket_info.url.path) + ) + + gt_dataset_path = tempdir / "gt.json" + gt_dataset_path.write_bytes(gt_annotations.read()) + self._gt_dataset = dm.Dataset.import_from( + os.fspath(gt_dataset_path), + format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + + def _validate_jobs(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + gt_dataset = self._require_field(self._gt_dataset) + job_annotations = self._require_field(self.job_annotations) job_results: Dict[int, float] = {} rejected_job_ids: List[int] = [] - with TemporaryDirectory() as tempdir: - tempdir = Path(tempdir) + comparator = DATASET_COMPARATOR_TYPE_MAP[manifest.annotation.type]( + min_similarity_threshold=manifest.validation.min_quality, + ) - gt_dataset_path = tempdir / "gt.json" - gt_dataset_path.write_bytes(gt_annotations.read()) - gt_dataset = dm.Dataset.import_from( - os.fspath(gt_dataset_path), format=DM_GT_DATASET_FORMAT_MAPPING[task_type] - ) + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) - comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), + format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], ) - for job_cvat_id, job_annotations_file in job_annotations.items(): - job_dataset_path = tempdir / str(job_cvat_id) - extract_zip_archive(job_annotations_file, job_dataset_path) - - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), format=dataset_format - ) - job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) job_results[job_cvat_id] = job_mean_accuracy - if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) - merged_dataset_path = tempdir / "merged" - merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - extract_zip_archive(merged_annotations, merged_dataset_path) + if job_mean_accuracy < manifest.validation.min_quality: + rejected_job_ids.append(job_cvat_id) - merged_dataset = dm.Dataset.import_from( - os.fspath(merged_dataset_path), format=merged_dataset_format - ) - put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + self._job_results = job_results + self._rejected_job_ids = rejected_job_ids - updated_merged_dataset_path = tempdir / "merged_updated" - merged_dataset.export( - updated_merged_dataset_path, merged_dataset_format, save_media=False - ) + def _prepare_merged_dataset(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + merged_annotations = self._require_field(self.merged_annotations) + gt_dataset = self._require_field(self._gt_dataset) - updated_merged_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) - updated_merged_dataset_archive.seek(0) + merged_dataset_path = tempdir / "merged" + merged_dataset_format = DM_DATASET_FORMAT_MAPPING[manifest.annotation.type] + extract_zip_archive(merged_annotations, merged_dataset_path) + + merged_dataset = dm.Dataset.import_from( + os.fspath(merged_dataset_path), format=merged_dataset_format + ) + self._put_gt_into_merged_dataset(gt_dataset, merged_dataset, manifest=manifest) + + updated_merged_dataset_path = tempdir / "merged_updated" + merged_dataset.export(updated_merged_dataset_path, merged_dataset_format, save_media=False) + + updated_merged_dataset_archive = io.BytesIO() + write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) + updated_merged_dataset_archive.seek(0) + + self._updated_merged_dataset_archive = updated_merged_dataset_archive + + @classmethod + def _put_gt_into_merged_dataset( + cls, gt_dataset: dm.Dataset, merged_dataset: dm.Dataset, *, manifest: TaskManifest + ): + """ + Updates the merged dataset inplace, writing GT annotations corresponding to the task type. + """ + + match manifest.annotation.type: + case TaskType.image_boxes.value: + merged_dataset.update(gt_dataset) + case TaskType.image_points.value: + for sample in gt_dataset: + annotations = [ + # Put a point in the center of each GT bbox + # Not ideal, but it's the target for now + dm.Points( + [bbox.x + bbox.w / 2, bbox.y + bbox.h / 2], + label=bbox.label, + attributes=bbox.attributes, + ) + for bbox in sample.annotations + if isinstance(bbox, dm.Bbox) + ] + merged_dataset.put(sample.wrap(annotations=annotations)) + case TaskType.image_label_binary.value: + merged_dataset.update(gt_dataset) + case TaskType.image_boxes_from_points: + merged_dataset.update(gt_dataset) + case TaskType.image_skeletons_from_boxes: + # The original behavior is broken for skeletons + gt_dataset = dm.Dataset(gt_dataset) + gt_dataset = gt_dataset.transform( + ProjectLabels, dst_labels=merged_dataset.categories()[dm.AnnotationType.label] + ) + merged_dataset.update(gt_dataset) + case _: + assert False, f"Unknown task type {manifest.annotation.type}" - return job_results, rejected_job_ids, updated_merged_dataset_archive + def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: + with TemporaryDirectory() as tempdir: + self._temp_dir = Path(tempdir) + + self._parse_gt() + + self._validate_jobs() + job_results = self._require_field(job_results) + rejected_job_ids = self._require_field(self._rejected_job_ids) + + self._prepare_merged_dataset() + updated_merged_dataset_archive = self._require_field( + self._updated_merged_dataset_archive + ) + + return ( + job_results, + rejected_job_ids, + updated_merged_dataset_archive, + ) class _BoxesFromPointsValidator(_TaskValidator): @@ -160,8 +239,8 @@ def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): points_dataset, ) = self._download_task_meta() - self.gt_dataset = gt_dataset - self.points_dataset = points_dataset + self._gt_dataset = gt_dataset + self._points_dataset = points_dataset point_key_to_sample = { skeleton.id: sample @@ -261,7 +340,7 @@ def _download_task_meta(self): return boxes_to_points_mapping, roi_filenames, rois, gt_dataset, points_dataset def _make_gt_dataset_for_job(self, job_dataset: dm.Dataset) -> dm.Dataset: - job_gt_dataset = dm.Dataset(categories=self.gt_dataset.categories(), media_type=dm.Image) + job_gt_dataset = dm.Dataset(categories=self._gt_dataset.categories(), media_type=dm.Image) for job_sample in job_dataset: roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] @@ -305,7 +384,7 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: tempdir = Path(tempdir) comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality + min_similarity_threshold=manifest.validation.min_quality, ) for job_cvat_id, job_annotations_file in job_annotations.items(): @@ -330,7 +409,7 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: merged_dataset = dm.Dataset.import_from( os.fspath(merged_dataset_path), format=merged_dataset_format ) - put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) + self._put_gt_into_merged_dataset(self._gt_dataset, merged_dataset, manifest=manifest) updated_merged_dataset_path = tempdir / "merged_updated" merged_dataset.export( @@ -341,7 +420,11 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) updated_merged_dataset_archive.seek(0) - return job_results, rejected_job_ids, updated_merged_dataset_archive + return ( + job_results, + rejected_job_ids, + updated_merged_dataset_archive, + ) class _SkeletonsFromBoxesValidator(_TaskValidator): @@ -593,7 +676,7 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: merged_dataset = dm.Dataset.import_from( os.fspath(merged_dataset_path), format=merged_dataset_format ) - put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) + self._put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) updated_merged_dataset_path = tempdir / "merged_updated" merged_dataset.export( @@ -604,7 +687,11 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) updated_merged_dataset_archive.seek(0) - return job_results, rejected_job_ids, updated_merged_dataset_archive + return ( + job_results, + rejected_job_ids, + updated_merged_dataset_archive, + ) def process_intermediate_results( @@ -707,45 +794,6 @@ def process_intermediate_results( ) -def put_gt_into_merged_dataset( - gt_dataset: dm.Dataset, merged_dataset: dm.Dataset, *, manifest: TaskManifest -): - """ - Updates the merged dataset inplace, writing GT annotations corresponding to the task type. - """ - - match manifest.annotation.type: - case TaskType.image_boxes.value: - merged_dataset.update(gt_dataset) - case TaskType.image_points.value: - for sample in gt_dataset: - annotations = [ - # Put a point in the center of each GT bbox - # Not ideal, but it's the target for now - dm.Points( - [bbox.x + bbox.w / 2, bbox.y + bbox.h / 2], - label=bbox.label, - attributes=bbox.attributes, - ) - for bbox in sample.annotations - if isinstance(bbox, dm.Bbox) - ] - merged_dataset.put(sample.wrap(annotations=annotations)) - case TaskType.image_label_binary.value: - merged_dataset.update(gt_dataset) - case TaskType.image_boxes_from_points: - merged_dataset.update(gt_dataset) - case TaskType.image_skeletons_from_boxes: - # The original behavior is broken for skeletons - gt_dataset = dm.Dataset(gt_dataset) - gt_dataset = gt_dataset.transform( - ProjectLabels, dst_labels=merged_dataset.categories()[dm.AnnotationType.label] - ) - merged_dataset.update(gt_dataset) - case _: - assert False, f"Unknown task type {manifest.annotation.type}" - - def parse_annotation_metafile(metafile: io.RawIOBase) -> AnnotationMeta: return AnnotationMeta.parse_raw(metafile.read()) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index ec9030f8ff..5b795c04c4 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -53,7 +53,6 @@ def __init__( self.annotation_meta: Optional[annotation.AnnotationMeta] = None self.job_annotations: Optional[Dict[int, bytes]] = None self.merged_annotations: Optional[bytes] = None - self.gt_data: Optional[bytes] = None def set_logger(self, logger: Logger): self.logger = logger @@ -99,15 +98,9 @@ def _download_annotations(self): self.job_annotations = job_annotations self.merged_annotations = merged_annotations - def _download_gt(self): - gt_bucket = BucketAccessInfo.from_raw_url(self.manifest.validation.gt_url) - gt_bucket_client = make_cloud_client(gt_bucket) - self.gt_data = gt_bucket_client.download_file(gt_bucket.url.bucket_name, gt_bucket.url.path) - def _download_results(self): self._download_results_meta() self._download_annotations() - self._download_gt() ValidationResult = Union[ValidationSuccess, ValidationFailure] @@ -115,7 +108,6 @@ def _process_annotation_results(self) -> ValidationResult: assert self.annotation_meta is not None assert self.job_annotations is not None assert self.merged_annotations is not None - assert self.gt_data is not None # TODO: refactor further return process_intermediate_results( @@ -125,7 +117,6 @@ def _process_annotation_results(self) -> ValidationResult: meta=self.annotation_meta, job_annotations={k: io.BytesIO(v) for k, v in self.job_annotations.items()}, merged_annotations=io.BytesIO(self.merged_annotations), - gt_annotations=io.BytesIO(self.gt_data), manifest=self.manifest, logger=self.logger, ) From a42b71b93e0737c6273359cb1cd351a2a5986d98 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 22 Feb 2024 15:39:53 +0300 Subject: [PATCH 47/82] Add gcs support - fixes (#1) * Update code formatting * Update poetry lock * Refactor * Use dict for gcs file contents * Update class name * Add backward compatibility for data bucket env var * Remove extra changes * Remove extra import * Fix enum name * Update bucket access info parsing * Fix error type in cloud provider parsing * Update tests --- .../cvat/exchange-oracle/src/core/config.py | 38 ++---- .../cvat/exchange-oracle/src/core/manifest.py | 37 +++++- .../src/crons/state_trackers.py | 6 +- .../exchange-oracle/src/cvat/api_calls.py | 20 +-- .../src/handlers/job_creation.py | 45 ++++--- .../src/handlers/job_export.py | 6 +- .../src/services/cloud/__init__.py | 3 +- .../src/services/cloud/client.py | 2 +- .../exchange-oracle/src/services/cloud/gcs.py | 11 +- .../exchange-oracle/src/services/cloud/s3.py | 6 +- .../src/services/cloud/types.py | 120 ++++++++++-------- .../src/services/cloud/utils.py | 27 ++-- .../test_retrieve_annotations.py | 5 +- .../cvat/recording-oracle/src/core/config.py | 11 +- .../recording-oracle/src/core/manifest.py | 41 +++++- .../handlers/process_intermediate_results.py | 14 +- .../src/handlers/validation.py | 15 +-- .../src/services/cloud/__init__.py | 3 +- .../src/services/cloud/client.py | 2 +- .../src/services/cloud/gcs.py | 11 +- .../recording-oracle/src/services/cloud/s3.py | 7 +- .../src/services/cloud/types.py | 120 ++++++++++-------- .../src/services/cloud/utils.py | 29 ++--- .../services/cloud/test_client_service.py | 6 +- 24 files changed, 326 insertions(+), 259 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index 0ea1c16115..a5953b849e 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -46,18 +46,14 @@ class LocalhostConfig: "LOCALHOST_PRIVATE_KEY", "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) - addr = os.environ.get( - "LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - ) + addr = os.environ.get("LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") job_launcher_url = os.environ.get("LOCALHOST_JOB_LAUNCHER_URL") recording_oracle_url = os.environ.get("LOCALHOST_RECORDING_ORACLE_URL") class CronConfig: - process_job_launcher_webhooks_int = int( - os.environ.get("PROCESS_JOB_LAUNCHER_WEBHOOKS_INT", 30) - ) + process_job_launcher_webhooks_int = int(os.environ.get("PROCESS_JOB_LAUNCHER_WEBHOOKS_INT", 30)) process_job_launcher_webhooks_chunk_size = os.environ.get( "PROCESS_JOB_LAUNCHER_WEBHOOKS_CHUNK_SIZE", 5 ) @@ -67,24 +63,16 @@ class CronConfig: process_recording_oracle_webhooks_chunk_size = os.environ.get( "PROCESS_RECORDING_ORACLE_WEBHOOKS_CHUNK_SIZE", 5 ) - track_completed_projects_int = int( - os.environ.get("TRACK_COMPLETED_PROJECTS_INT", 30) - ) - track_completed_projects_chunk_size = os.environ.get( - "TRACK_COMPLETED_PROJECTS_CHUNK_SIZE", 5 - ) + track_completed_projects_int = int(os.environ.get("TRACK_COMPLETED_PROJECTS_INT", 30)) + track_completed_projects_chunk_size = os.environ.get("TRACK_COMPLETED_PROJECTS_CHUNK_SIZE", 5) track_completed_tasks_int = int(os.environ.get("TRACK_COMPLETED_TASKS_INT", 30)) - track_creating_tasks_chunk_size = os.environ.get( - "TRACK_CREATING_TASKS_CHUNK_SIZE", 5 - ) + track_creating_tasks_chunk_size = os.environ.get("TRACK_CREATING_TASKS_CHUNK_SIZE", 5) track_creating_tasks_int = int(os.environ.get("TRACK_CREATING_TASKS_INT", 300)) track_assignments_int = int(os.environ.get("TRACK_ASSIGNMENTS_INT", 5)) track_assignments_chunk_size = os.environ.get("TRACK_ASSIGNMENTS_CHUNK_SIZE", 10) retrieve_annotations_int = int(os.environ.get("RETRIEVE_ANNOTATIONS_INT", 60)) - retrieve_annotations_chunk_size = os.environ.get( - "RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5 - ) + retrieve_annotations_chunk_size = os.environ.get("RETRIEVE_ANNOTATIONS_CHUNK_SIZE", 5) class CvatConfig: @@ -102,20 +90,22 @@ class CvatConfig: class StorageConfig: - # common attributes provider: ClassVar[str] = os.environ["STORAGE_PROVIDER"].lower() - data_bucket_name: ClassVar[str] = os.environ["STORAGE_RESULTS_BUCKET_NAME"] + data_bucket_name: ClassVar[str] = ( + os.environ.get("STORAGE_RESULTS_BUCKET_NAME") # backward compatibility + or os.environ["STORAGE_BUCKET_NAME"] + ) endpoint_url: ClassVar[str] = os.environ[ "STORAGE_ENDPOINT_URL" ] # TODO: probably should be optional region: ClassVar[Optional[str]] = os.environ.get("STORAGE_REGION") - results_dir_suffix: ClassVar[str] = os.environ.get( - "STORAGE_RESULTS_DIR_SUFFIX", "-results" - ) - secure: ClassVar[str] = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + results_dir_suffix: ClassVar[str] = os.environ.get("STORAGE_RESULTS_DIR_SUFFIX", "-results") + secure: ClassVar[bool] = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + # S3 specific attributes access_key: ClassVar[Optional[str]] = os.environ.get("STORAGE_ACCESS_KEY") secret_key: ClassVar[Optional[str]] = os.environ.get("STORAGE_SECRET_KEY") + # GCS specific attributes key_file_path: ClassVar[Optional[str]] = os.environ.get("STORAGE_KEY_FILE_PATH") diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index d4f8731624..506aed0a7f 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -1,5 +1,6 @@ from decimal import Decimal -from typing import Optional, Union, Dict +from enum import Enum +from typing import Annotated, Any, Dict, Literal, Optional, Union from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -7,12 +8,38 @@ from src.core.types import TaskType +class BucketProviders(str, Enum): + aws = "AWS" + gcs = "GCS" + + +class BucketUrlBase(BaseModel): + provider: BucketProviders + host_url: str + bucket_name: str + path: str = "" + + +class AwsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.aws] + access_key: str = "" # (optional) AWS Access key + secret_key: str = "" # (optional) AWS Secret key + + +class GcsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.gcs] + service_account_key: Dict[str, Any] = {} # (optional) Contents of GCS key file + + +BucketUrl = Annotated[Union[AwsBucketUrl, GcsBucketUrl], Field(discriminator="provider")] + + class DataInfo(BaseModel): - data_url: Union[AnyUrl, Dict] - "Bucket URL, AWS s3 | GCS, virtual-hosted-style access" + data_url: Union[AnyUrl, BucketUrl] + "Bucket URL, AWS S3 | GCS, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html - points_url: Optional[Union[AnyUrl, Dict]] = None + points_url: Optional[Union[AnyUrl, BucketUrl]] = None "A path to an archive with a set of points in COCO Keypoints format, " "which provides information about all objects on images" @@ -57,7 +84,7 @@ class ValidationInfo(BaseModel): val_size: int = Field(default=2, gt=0) "Validation frames per job" - gt_url: Union[AnyUrl, Dict] + gt_url: Union[AnyUrl, BucketUrl] "URL to the archive with Ground Truth annotations, the format is COCO keypoints" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 3d1e9f9197..d05971624a 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -1,8 +1,8 @@ from typing import Dict, List import src.cvat.api_calls as cvat_api -import src.services.cloud as cloud_service import src.models.cvat as cvat_models +import src.services.cloud as cloud_service import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service from src.chain.escrow import get_escrow_manifest, validate_escrow @@ -23,7 +23,7 @@ prepare_annotation_metafile, ) from src.log import ROOT_LOGGER_NAME -from src.services.cloud.types import BucketAccessInfo +from src.services.cloud import BucketAccessInfo from src.utils.assignments import parse_manifest from src.utils.logging import get_function_logger @@ -190,7 +190,7 @@ def retrieve_annotations() -> None: Retrieves and stores completed annotations: 1. Retrieves annotations from projects with "completed" status 2. Postprocesses them - 3. Stores annotations in S3/GCS bucket + 3. Stores annotations in the oracle bucket 4. Prepares a webhook to recording oracle """ logger = get_function_logger(module_logger) diff --git a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py index 957a8267aa..f48ed5253d 100644 --- a/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py +++ b/packages/examples/cvat/exchange-oracle/src/cvat/api_calls.py @@ -112,25 +112,27 @@ def create_cloudstorage( ) -> models.CloudStorageRead: # credentials: access_key | secret_key | service_account_key # CVAT credentials: key | secret_key | key_file - def to_cvat_credentials() -> Dict: - credentials_ = dict() - for cvat_field, field in zip( - ("key", "secret_key", "key_file"), ("access_key", "secret_key", "service_account_key") - ): + def _to_cvat_credentials(credentials: Dict[str, Any]) -> Dict: + cvat_credentials = dict() + for cvat_field, field in { + "key": "access_key", + "secret_key": "secret_key", + "key_file": "service_account_key", + }.items(): if value := credentials.get(field): if cvat_field == "key_file": key_file = BytesIO(json.dumps(value).encode("utf-8")) key_file.name = "key_file.json" key_file.seek(0) - credentials_[cvat_field] = key_file + cvat_credentials[cvat_field] = key_file else: - credentials_[cvat_field] = value - return credentials_ + cvat_credentials[cvat_field] = value + return cvat_credentials request_kwargs = dict() if credentials: - request_kwargs.update(to_cvat_credentials()) + request_kwargs.update(_to_cvat_credentials(credentials)) credentials_type = ( models.CredentialsTypeEnum("KEY_SECRET_KEY_PAIR") if provider == "AWS_S3_BUCKET" diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 6bc7c23afd..5c657965e5 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -27,7 +27,7 @@ from src.core.types import CvatLabelType, TaskStatus, TaskType from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME -from src.services.cloud import CloudProvider, StorageClient +from src.services.cloud import CloudProviders, StorageClient from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger @@ -56,11 +56,6 @@ TaskType.image_boxes_from_points: "coco_instances", } -CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { - CloudProvider.aws: "AWS_S3_BUCKET", - CloudProvider.gcs: "GOOGLE_CLOUD_STORAGE", -} - class DatasetValidationError(Exception): pass @@ -186,9 +181,9 @@ def _download_input_data(self): data_filenames = data_storage_client.list_files(prefix=data_bucket.path) self.input_filenames = filter_image_files(data_filenames) - self.input_gt_data = gt_storage_client.download_fileobj(gt_bucket.path) + self.input_gt_data = gt_storage_client.download_file(gt_bucket.path) - self.input_points_data = points_storage_client.download_fileobj(points_bucket.path) + self.input_points_data = points_storage_client.download_file(points_bucket.path) def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) @@ -777,7 +772,7 @@ def _extract_and_upload_rois(self): if not image_roi_infos: continue - image_bytes = src_client.download_fileobj(os.path.join(src_prefix, filename)) + image_bytes = src_client.download_file(os.path.join(src_prefix, filename)) image_pixels = decode_image(image_bytes) sample = filename_to_sample[filename] @@ -819,10 +814,7 @@ def _create_on_cvat(self): # Register cloud storage on CVAT to pass user dataset cloud_storage = cvat_api.create_cloudstorage( - CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[oracle_bucket.provider], - oracle_bucket.bucket_name, - bucket_host=oracle_bucket.host_url, - **({ "credentials": oracle_bucket.credentials.to_dict() } if oracle_bucket.credentials else {}) + **_make_cvat_cloud_storage_params(oracle_bucket) ) # Create a project @@ -984,6 +976,24 @@ def make_label_configuration(manifest: TaskManifest) -> List[dict]: ] +def _make_cvat_cloud_storage_params(bucket_info: BucketAccessInfo) -> Dict: + CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER = { + CloudProviders.aws: "AWS_S3_BUCKET", + CloudProviders.gcs: "GOOGLE_CLOUD_STORAGE", + } + + params = { + "provider": CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[bucket_info.provider], + "bucket_name": bucket_info.bucket_name, + "bucket_host": bucket_info.host_url, + } + + if bucket_info.credentials: + params["credentials"] = bucket_info.credentials.to_dict() + + return params + + def create_task(escrow_address: str, chain_id: int) -> None: logger = get_function_logger(module_logger) @@ -1006,7 +1016,7 @@ def create_task(escrow_address: str, chain_id: int) -> None: ) data_filenames = filter_image_files(data_filenames) - gt_file_data = gt_bucket_client.download_fileobj( + gt_file_data = gt_bucket_client.download_file( gt_bucket.path, ) @@ -1017,12 +1027,7 @@ def create_task(escrow_address: str, chain_id: int) -> None: label_configuration = make_label_configuration(manifest) # Register cloud storage on CVAT to pass user dataset - cloud_storage = cvat_api.create_cloudstorage( - CLOUD_PROVIDER_TO_CVAT_CLOUD_PROVIDER[data_bucket.provider], - data_bucket.bucket_name, - bucket_host=data_bucket.host_url, - **({ "credentials": data_bucket.credentials.to_dict() } if data_bucket.credentials else {}) - ) + cloud_storage = cvat_api.create_cloudstorage(**_make_cvat_cloud_storage_params(data_bucket)) # Create a project project = cvat_api.create_project( diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 968227b4a3..a114111b9b 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -190,7 +190,7 @@ def _download_task_meta(self): storage_client = make_cloud_client(oracle_data_bucket) roi_filenames = serializer.parse_roi_filenames( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME ), @@ -198,7 +198,7 @@ def _download_task_meta(self): ) rois = serializer.parse_roi_info( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME ), @@ -206,7 +206,7 @@ def _download_task_meta(self): ) points_dataset = serializer.parse_points_annotations( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.POINTS_FILENAME ), diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py index a1362d3db1..e492c74156 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/__init__.py @@ -1,4 +1,3 @@ from src.services.cloud.client import StorageClient -from src.services.cloud.s3 import S3Client -from src.services.cloud.types import CloudProvider +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, CloudProviders from src.services.cloud.utils import make_client diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py index f1e822c0c9..5bb92d77e3 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/client.py @@ -23,7 +23,7 @@ def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: ... @abstractmethod - def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: ... @abstractmethod diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py index d76be0353c..36611b363f 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/gcs.py @@ -1,7 +1,3 @@ -# Copyright (C) 2024 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - from io import BytesIO from typing import Dict, List, Optional from urllib.parse import unquote @@ -10,8 +6,10 @@ from src.services.cloud.client import StorageClient +DEFAULT_GCS_HOST = "storage.googleapis.com" + -class GCSClient(StorageClient): +class GcsClient(StorageClient): def __init__( self, *, @@ -19,6 +17,7 @@ def __init__( service_account_key: Optional[Dict] = None, ) -> None: super().__init__(bucket) + if service_account_key: self.client = storage.Client.from_service_account_info(service_account_key) else: @@ -39,7 +38,7 @@ def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: bucket_client = self.client.get_bucket(bucket) return bucket_client.blob(unquote(key)).exists() - def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: bucket = unquote(bucket) if bucket else self._bucket bucket_client = self.client.get_bucket(bucket) blob = bucket_client.blob(unquote(key)) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py index 65c6752b60..e8e608ce99 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/s3.py @@ -1,7 +1,3 @@ -# Copyright (C) 2023-2024 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - from io import BytesIO from typing import List, Optional from urllib.parse import unquote @@ -57,7 +53,7 @@ def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: else: raise - def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: bucket = unquote(bucket) if bucket else self._bucket with BytesIO() as data: self.client.download_fileobj(Bucket=bucket, Key=unquote(key), Fileobj=data) diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py index 1c3cb2e87b..5f5b0db3ad 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/types.py @@ -1,54 +1,62 @@ from __future__ import annotations import json -from dataclasses import dataclass +from dataclasses import asdict, dataclass, is_dataclass from enum import Enum, auto +from inspect import isclass from typing import Dict, Optional, Type, Union from urllib.parse import urlparse +from src.core import manifest from src.core.config import Config, StorageConfig +from src.services.cloud.gcs import DEFAULT_GCS_HOST from src.services.cloud.s3 import DEFAULT_S3_HOST from src.utils.enums import BetterEnumMeta from src.utils.net import is_ipv4 -class CloudProvider(Enum, metaclass=BetterEnumMeta): +class CloudProviders(Enum, metaclass=BetterEnumMeta): aws = auto() gcs = auto() @classmethod - def list(cls): - return [x.value for x in cls] - - @classmethod - def from_str(cls, provider: str) -> CloudProvider: - match provider: - case "aws": - return CloudProvider.aws - case "gcs": - return CloudProvider.gcs - case _: - raise ValueError( - f"The '{provider}' is not supported provider. List with supported providers: {cls.list()}" - ) + def from_str(cls, provider: str) -> CloudProviders: + try: + return cls[provider.lower()] + except KeyError: + raise ValueError( + f"The '{provider}' is not supported. " + f"List with supported providers: {', '.join(x.name for x in cls)}" + ) class BucketCredentials: def to_dict(self) -> Dict: - return self.__dict__ + if not is_dataclass(self): + raise NotImplementedError + + return asdict(self) - def from_storage_config(config: Type[StorageConfig]) -> Optional[BucketCredentials]: + @classmethod + def from_storage_config(cls, config: Type[StorageConfig]) -> Optional[BucketCredentials]: credentials = None - if config.access_key and config.secret_key and config.provider != "aws": + if (config.access_key or config.secret_key) and config.provider.lower() != "aws": raise ValueError( - "Wrong storage configuration. The access_key/secret_key pair" + "Invalid storage configuration. The access_key/secret_key pair" f"cannot be specified with {config.provider} provider" ) + elif ( + bool(config.access_key) ^ bool(config.secret_key) + ) and config.provider.lower() == "aws": + raise ValueError( + "Invalid storage configuration. " + "Either none or both access_key and secret_key must be specified for an AWS storage" + ) - if config.key_file_path and config.provider != "gcs": + if config.key_file_path and config.provider.lower() != "gcs": raise ValueError( - "Wrong storage configuration. The key_file_path" + "Invalid storage configuration. The key_file_path" f"cannot be specified with {config.provider} provider" ) @@ -56,13 +64,13 @@ def from_storage_config(config: Type[StorageConfig]) -> Optional[BucketCredentia credentials = S3BucketCredentials(config.access_key, config.secret_key) elif config.key_file_path: with open(config.key_file_path, "rb") as f: - credentials = GCSCredentials(json.load(f)) + credentials = GcsBucketCredentials(json.load(f)) return credentials @dataclass -class GCSCredentials(BucketCredentials): +class GcsBucketCredentials(BucketCredentials): service_account_key: Dict @@ -74,31 +82,32 @@ class S3BucketCredentials(BucketCredentials): @dataclass class BucketAccessInfo: - provider: CloudProvider + provider: CloudProviders host_url: str bucket_name: str path: Optional[str] = None credentials: Optional[BucketCredentials] = None @classmethod - def from_url(cls, data: str) -> BucketAccessInfo: - parsed_url = urlparse(data) + def from_url(cls, url: str) -> BucketAccessInfo: + parsed_url = urlparse(url) if parsed_url.netloc.endswith(DEFAULT_S3_HOST): # AWS S3 bucket return BucketAccessInfo( - provider=CloudProvider.aws, + provider=CloudProviders.aws, host_url=f"https://{DEFAULT_S3_HOST}", bucket_name=parsed_url.netloc.split(".")[0], path=parsed_url.path.lstrip("/"), ) - elif parsed_url.netloc.endswith("storage.googleapis.com"): + elif parsed_url.netloc.endswith(DEFAULT_GCS_HOST): # Google Cloud Storage (GCS) bucket - # NOTE: virtual hosted-style is expected (https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME) + # Virtual hosted-style is expected: + # https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME return BucketAccessInfo( - provider=CloudProvider.gcs, - bucket_name=parsed_url.netloc[: -len(".storage.googleapis.com")], - host_url=f"{parsed_url.scheme}://storage.googleapis.com", + provider=CloudProviders.gcs, + bucket_name=parsed_url.netloc[: -len(f".{DEFAULT_GCS_HOST}")], + host_url=f"{parsed_url.scheme}://{DEFAULT_GCS_HOST}", path=parsed_url.path.lstrip("/"), ) elif Config.features.enable_custom_cloud_host: @@ -111,36 +120,41 @@ def from_url(cls, data: str) -> BucketAccessInfo: path = parsed_url.path.lstrip("/") return BucketAccessInfo( - provider=CloudProvider.aws, + provider=CloudProviders.aws, host_url=f"{parsed_url.scheme}://{host}", bucket_name=bucket_name, path=path, ) else: - raise ValueError( - f"{parsed_url.netloc} cloud provider is not supported by CVAT." - ) + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported.") @classmethod - def from_dict(cls, data: Dict) -> BucketAccessInfo: + def _from_dict(cls, data: Dict) -> BucketAccessInfo: for required_field in ( "provider", "bucket_name", ): if required_field not in data: raise ValueError( - f"The {required_field} is required and is not specified in the bucket configuration" + f"The {required_field} is required and is not " + "specified in the bucket configuration" ) - data["provider"] = CloudProvider.from_str(data["provider"].lower()) + provider = CloudProviders.from_str(data["provider"]) + data["provider"] = provider + + if provider == CloudProviders.aws: + access_key = data.pop("access_key", None) + secret_key = data.pop("secret_key", None) + if bool(access_key) ^ bool(secret_key): + raise ValueError("access_key and secret_key can only be used together") - if (access_key := data.pop("access_key", None)) and ( - secret_key := data.pop("secret_key", None) - ): data["credentials"] = S3BucketCredentials(access_key, secret_key) - elif service_account_key := data.pop("service_account_key", None): - data["credentials"] = GCSCredentials(service_account_key) + elif provider == CloudProviders.gcs and ( + service_account_key := data.pop("service_account_key", None) + ): + data["credentials"] = GcsBucketCredentials(service_account_key) return BucketAccessInfo(**data) @@ -149,19 +163,25 @@ def from_storage_config(cls, config: Type[StorageConfig]) -> BucketAccessInfo: credentials = BucketCredentials.from_storage_config(config) return BucketAccessInfo( - provider=CloudProvider.from_str(config.provider), + provider=CloudProviders.from_str(config.provider), host_url=config.provider_endpoint_url(), bucket_name=config.data_bucket_name, credentials=credentials, ) @classmethod - def parse_obj(cls, data: Union[Dict, str, Type[StorageConfig]]) -> BucketAccessInfo: - if isinstance(data, Dict): - return cls.from_dict(data) + def from_bucket_url(cls, bucket_url: manifest.BucketUrl) -> BucketAccessInfo: + return cls._from_dict(bucket_url.dict()) + + @classmethod + def parse_obj( + cls, data: Union[str, Type[StorageConfig], manifest.BucketUrl] + ) -> BucketAccessInfo: + if isinstance(data, manifest.BucketUrlBase): + return cls.from_bucket_url(data) elif isinstance(data, str): return cls.from_url(data) - elif issubclass(data, StorageConfig): + elif isclass(data) and issubclass(data, StorageConfig): return cls.from_storage_config(data) raise TypeError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py index bd2b3ad88a..a9f821d174 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cloud/utils.py @@ -1,19 +1,19 @@ from typing import Optional from src.services.cloud.client import StorageClient -from src.services.cloud.gcs import GCSClient -from src.services.cloud.s3 import S3Client -from src.services.cloud.types import BucketAccessInfo, CloudProvider +from src.services.cloud.gcs import DEFAULT_GCS_HOST, GcsClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, CloudProviders def compose_bucket_url( - bucket_name: str, provider: CloudProvider, *, bucket_host: Optional[str] = None + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None ) -> str: match provider: - case CloudProvider.aws: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProvider.gcs: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or DEFAULT_S3_HOST}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or DEFAULT_GCS_HOST}/" def make_client( @@ -24,20 +24,21 @@ def make_client( } match bucket_info.provider: - case CloudProvider.aws: - ClientClass = S3Client + case CloudProviders.aws: + client_type = S3Client if bucket_info.credentials: client_kwargs["access_key"] = bucket_info.credentials.access_key client_kwargs["secret_key"] = bucket_info.credentials.secret_key + if bucket_info.host_url: client_kwargs["endpoint_url"] = bucket_info.host_url - case CloudProvider.gcs: - ClientClass = GCSClient + case CloudProviders.gcs: + client_type = GcsClient if bucket_info.credentials: client_kwargs["service_account_key"] = bucket_info.credentials.service_account_key case _: raise ValueError(f"Unsupported cloud provider ({bucket_info.provider}) was provided") - return ClientClass(**client_kwargs) + return client_type(**client_kwargs) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py index 0678178529..accbe8957e 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_retrieve_annotations.py @@ -130,7 +130,6 @@ def test_retrieve_annotations(self): mock_storage_client.create_file = Mock() mock_storage_client.list_files = Mock(return_value=[]) mock_cloud_service.make_client = Mock(return_value=mock_storage_client) - mock_cloud_service.S3Client = Mock(return_value=mock_storage_client) retrieve_annotations() @@ -251,12 +250,12 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): patch("src.crons.state_trackers.cvat_api"), patch("src.crons.state_trackers.cvat_api.get_job_annotations") as mock_annotations, patch("src.crons.state_trackers.validate_escrow"), - patch("src.crons.state_trackers.cloud_service.S3Client") as mock_S3Client, + patch("src.crons.state_trackers.cloud_service.make_client") as mock_make_client, ): manifest = json.load(data) mock_get_manifest.return_value = manifest mock_create_file = Mock() - mock_S3Client.return_value.create_file = mock_create_file + mock_make_client.return_value.create_file = mock_create_file mock_annotations.side_effect = Exception("Connection error") retrieve_annotations() diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index c31616750b..1d3759de8f 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -46,9 +46,7 @@ class LocalhostConfig: "LOCALHOST_PRIVATE_KEY", "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ) - addr = os.environ.get( - "LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - ) + addr = os.environ.get("LOCALHOST_MUMBAI_ADDR", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") exchange_oracle_url = os.environ.get("LOCALHOST_EXCHANGE_ORACLE_URL") reputation_oracle_url = os.environ.get("LOCALHOST_REPUTATION_ORACLE_URL") @@ -98,17 +96,16 @@ def bucket_url(cls) -> str: class StorageConfig(IStorageConfig): - # common attributes provider = os.environ["STORAGE_PROVIDER"].lower() - endpoint_url = os.environ[ - "STORAGE_ENDPOINT_URL" - ] # TODO: probably should be optional + endpoint_url = os.environ["STORAGE_ENDPOINT_URL"] # TODO: probably should be optional region = os.environ.get("STORAGE_REGION") data_bucket_name = os.environ["STORAGE_RESULTS_BUCKET_NAME"] secure = to_bool(os.environ.get("STORAGE_USE_SSL", "true")) + # AWS S3 specific attributes access_key = os.environ.get("STORAGE_ACCESS_KEY") secret_key = os.environ.get("STORAGE_SECRET_KEY") + # GCS specific attributes key_file_path = os.environ.get("STORAGE_KEY_FILE_PATH") diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 00276e8594..506aed0a7f 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -1,5 +1,6 @@ from decimal import Decimal -from typing import Optional, Union, Dict +from enum import Enum +from typing import Annotated, Any, Dict, Literal, Optional, Union from pydantic import AnyUrl, BaseModel, Field, root_validator @@ -7,11 +8,41 @@ from src.core.types import TaskType +class BucketProviders(str, Enum): + aws = "AWS" + gcs = "GCS" + + +class BucketUrlBase(BaseModel): + provider: BucketProviders + host_url: str + bucket_name: str + path: str = "" + + +class AwsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.aws] + access_key: str = "" # (optional) AWS Access key + secret_key: str = "" # (optional) AWS Secret key + + +class GcsBucketUrl(BucketUrlBase, BaseModel): + provider: Literal[BucketProviders.gcs] + service_account_key: Dict[str, Any] = {} # (optional) Contents of GCS key file + + +BucketUrl = Annotated[Union[AwsBucketUrl, GcsBucketUrl], Field(discriminator="provider")] + + class DataInfo(BaseModel): - data_url: Union[AnyUrl, Dict] - "Bucket URL, s3 only, virtual-hosted-style access" + data_url: Union[AnyUrl, BucketUrl] + "Bucket URL, AWS S3 | GCS, virtual-hosted-style access" # https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html + points_url: Optional[Union[AnyUrl, BucketUrl]] = None + "A path to an archive with a set of points in COCO Keypoints format, " + "which provides information about all objects on images" + class LabelInfo(BaseModel): name: str @@ -33,7 +64,7 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: Optional[int] + max_time: int = Field(default_factory=lambda: Config.core_config.default_assignment_time) "Maximum time per job (assignment) for an annotator, in seconds" @root_validator @@ -53,7 +84,7 @@ class ValidationInfo(BaseModel): val_size: int = Field(default=2, gt=0) "Validation frames per job" - gt_url: Union[AnyUrl, Dict] + gt_url: Union[AnyUrl, BucketUrl] "URL to the archive with Ground Truth annotations, the format is COCO keypoints" diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index cf051e462d..e348ebe6a8 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -199,14 +199,12 @@ def _download_task_meta(self): layout = boxes_from_points_task.TaskMetaLayout() serializer = boxes_from_points_task.TaskMetaSerializer() - oracle_data_bucket = BucketAccessInfo.parse_obj( - Config.exchange_oracle_storage_config - ) + oracle_data_bucket = BucketAccessInfo.parse_obj(Config.exchange_oracle_storage_config) storage_client = make_cloud_client(oracle_data_bucket) boxes_to_points_mapping = serializer.parse_bbox_point_mapping( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.BBOX_POINT_MAPPING_FILENAME ), @@ -214,7 +212,7 @@ def _download_task_meta(self): ) roi_filenames = serializer.parse_roi_filenames( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_FILENAMES_FILENAME ), @@ -222,7 +220,7 @@ def _download_task_meta(self): ) rois = serializer.parse_roi_info( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.ROI_INFO_FILENAME ), @@ -230,7 +228,7 @@ def _download_task_meta(self): ) gt_dataset = serializer.parse_gt_annotations( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.GT_FILENAME ), @@ -238,7 +236,7 @@ def _download_task_meta(self): ) points_dataset = serializer.parse_points_annotations( - storage_client.download_fileobj( + storage_client.download_file( compose_data_bucket_filename( self.escrow_address, self.chain_id, layout.POINTS_FILENAME ), diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index c17bfaddad..7dc532f27a 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -27,7 +27,6 @@ ) from src.log import ROOT_LOGGER_NAME from src.services.cloud import make_client as make_cloud_client -from src.services.cloud import s3 from src.services.cloud.utils import BucketAccessInfo from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest from src.utils.logging import NullLogger, get_function_logger @@ -46,9 +45,7 @@ def __init__( self.db_session = db_session self.logger: Logger = NullLogger() - self.data_bucket = BucketAccessInfo.parse_obj( - Config.exchange_oracle_storage_config - ) + self.data_bucket = BucketAccessInfo.parse_obj(Config.exchange_oracle_storage_config) self.annotation_meta: Optional[annotation.AnnotationMeta] = None self.job_annotations: Optional[Dict[int, bytes]] = None @@ -66,9 +63,7 @@ def _download_results_meta(self): self.chain_id, annotation.ANNOTATION_RESULTS_METAFILE_NAME, ) - annotation_metafile_data = data_bucket_client.download_fileobj( - annotation_meta_path - ) + annotation_metafile_data = data_bucket_client.download_file(annotation_meta_path) self.annotation_meta = parse_annotation_metafile(io.BytesIO(annotation_metafile_data)) def _download_annotations(self): @@ -83,14 +78,14 @@ def _download_annotations(self): self.chain_id, job_meta.annotation_filename, ) - job_annotations[job_meta.job_id] = data_bucket_client.download_fileobj(job_filename) + job_annotations[job_meta.job_id] = data_bucket_client.download_file(job_filename) excor_merged_annotation_path = compose_annotation_results_bucket_filename( self.escrow_address, self.chain_id, annotation.RESULTING_ANNOTATIONS_FILE, ) - merged_annotations = data_bucket_client.download_fileobj(excor_merged_annotation_path) + merged_annotations = data_bucket_client.download_file(excor_merged_annotation_path) self.job_annotations = job_annotations self.merged_annotations = merged_annotations @@ -98,7 +93,7 @@ def _download_annotations(self): def _download_gt(self): gt_bucket = BucketAccessInfo.parse_obj(self.manifest.validation.gt_url) gt_bucket_client = make_cloud_client(gt_bucket) - self.gt_data = gt_bucket_client.download_fileobj(gt_bucket.path) + self.gt_data = gt_bucket_client.download_file(gt_bucket.path) def _download_results(self): self._download_results_meta() diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py index 45a193e20b..e492c74156 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/__init__.py @@ -1,4 +1,3 @@ from src.services.cloud.client import StorageClient -from src.services.cloud.types import CloudProvider +from src.services.cloud.types import BucketAccessInfo, BucketCredentials, CloudProviders from src.services.cloud.utils import make_client -from src.services.cloud.s3 import S3Client diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py index f1e822c0c9..5bb92d77e3 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/client.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/client.py @@ -23,7 +23,7 @@ def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: ... @abstractmethod - def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: ... @abstractmethod diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py index d76be0353c..36611b363f 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/gcs.py @@ -1,7 +1,3 @@ -# Copyright (C) 2024 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - from io import BytesIO from typing import Dict, List, Optional from urllib.parse import unquote @@ -10,8 +6,10 @@ from src.services.cloud.client import StorageClient +DEFAULT_GCS_HOST = "storage.googleapis.com" + -class GCSClient(StorageClient): +class GcsClient(StorageClient): def __init__( self, *, @@ -19,6 +17,7 @@ def __init__( service_account_key: Optional[Dict] = None, ) -> None: super().__init__(bucket) + if service_account_key: self.client = storage.Client.from_service_account_info(service_account_key) else: @@ -39,7 +38,7 @@ def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: bucket_client = self.client.get_bucket(bucket) return bucket_client.blob(unquote(key)).exists() - def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: bucket = unquote(bucket) if bucket else self._bucket bucket_client = self.client.get_bucket(bucket) blob = bucket_client.blob(unquote(key)) diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py index feb2ebe158..e8e608ce99 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/s3.py @@ -1,8 +1,3 @@ - -# Copyright (C) 2023-2024 CVAT.ai Corporation -# -# SPDX-License-Identifier: MIT - from io import BytesIO from typing import List, Optional from urllib.parse import unquote @@ -58,7 +53,7 @@ def file_exists(self, key: str, *, bucket: Optional[str] = None) -> bool: else: raise - def download_fileobj(self, key: str, *, bucket: Optional[str] = None) -> bytes: + def download_file(self, key: str, *, bucket: Optional[str] = None) -> bytes: bucket = unquote(bucket) if bucket else self._bucket with BytesIO() as data: self.client.download_fileobj(Bucket=bucket, Key=unquote(key), Fileobj=data) diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py index 74d4fabd07..ec8cd9df9e 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/types.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/types.py @@ -1,56 +1,62 @@ from __future__ import annotations import json -from dataclasses import dataclass +from dataclasses import asdict, dataclass, is_dataclass from enum import Enum, auto +from inspect import isclass from typing import Dict, Optional, Type, Union from urllib.parse import urlparse +from src.core import manifest from src.core.config import Config, IStorageConfig +from src.services.cloud.gcs import DEFAULT_GCS_HOST from src.services.cloud.s3 import DEFAULT_S3_HOST from src.utils.enums import BetterEnumMeta from src.utils.net import is_ipv4 -class CloudProvider(Enum, metaclass=BetterEnumMeta): +class CloudProviders(Enum, metaclass=BetterEnumMeta): aws = auto() gcs = auto() @classmethod - def list(cls): - return [x.value for x in cls] - - @classmethod - def from_str(cls, provider: str) -> CloudProvider: - match provider: - case "aws": - return CloudProvider.aws - case "gcs": - return CloudProvider.gcs - case _: - raise ValueError( - f"The '{provider}' is not supported provider. List with supported providers: {cls.list()}" - ) + def from_str(cls, provider: str) -> CloudProviders: + try: + return cls[provider.lower()] + except KeyError: + raise ValueError( + f"The '{provider}' is not supported. " + f"List with supported providers: {', '.join(x.name for x in cls)}" + ) class BucketCredentials: def to_dict(self) -> Dict: - return self.__dict__ + if not is_dataclass(self): + raise NotImplementedError + + return asdict(self) - def from_storage_config( - config: Type[IStorageConfig], - ) -> Optional[BucketCredentials]: + @classmethod + def from_storage_config(cls, config: Type[IStorageConfig]) -> Optional[BucketCredentials]: credentials = None - if config.access_key and config.secret_key and config.provider != "aws": + if (config.access_key or config.secret_key) and config.provider.lower() != "aws": raise ValueError( - "Wrong storage configuration. The access_key/secret_key pair" + "Invalid storage configuration. The access_key/secret_key pair" f"cannot be specified with {config.provider} provider" ) + elif ( + bool(config.access_key) ^ bool(config.secret_key) + ) and config.provider.lower() == "aws": + raise ValueError( + "Invalid storage configuration. " + "Either none or both access_key and secret_key must be specified for an AWS storage" + ) - if config.key_file_path and config.provider != "gcs": + if config.key_file_path and config.provider.lower() != "gcs": raise ValueError( - "Wrong storage configuration. The key_file_path" + "Invalid storage configuration. The key_file_path" f"cannot be specified with {config.provider} provider" ) @@ -58,13 +64,13 @@ def from_storage_config( credentials = S3BucketCredentials(config.access_key, config.secret_key) elif config.key_file_path: with open(config.key_file_path, "rb") as f: - credentials = GCSCredentials(json.load(f)) + credentials = GcsBucketCredentials(json.load(f)) return credentials @dataclass -class GCSCredentials(BucketCredentials): +class GcsBucketCredentials(BucketCredentials): service_account_key: Dict @@ -76,31 +82,32 @@ class S3BucketCredentials(BucketCredentials): @dataclass class BucketAccessInfo: - provider: CloudProvider + provider: CloudProviders host_url: str bucket_name: str path: Optional[str] = None credentials: Optional[BucketCredentials] = None @classmethod - def from_url(cls, data: str) -> BucketAccessInfo: - parsed_url = urlparse(data) + def from_url(cls, url: str) -> BucketAccessInfo: + parsed_url = urlparse(url) if parsed_url.netloc.endswith(DEFAULT_S3_HOST): # AWS S3 bucket return BucketAccessInfo( - provider=CloudProvider.aws, + provider=CloudProviders.aws, host_url=f"https://{DEFAULT_S3_HOST}", bucket_name=parsed_url.netloc.split(".")[0], path=parsed_url.path.lstrip("/"), ) - elif parsed_url.netloc.endswith("storage.googleapis.com"): + elif parsed_url.netloc.endswith(DEFAULT_GCS_HOST): # Google Cloud Storage (GCS) bucket - # NOTE: virtual hosted-style is expected (https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME) + # Virtual hosted-style is expected: + # https://BUCKET_NAME.storage.googleapis.com/OBJECT_NAME return BucketAccessInfo( - provider=CloudProvider.gcs, - bucket_name=parsed_url.netloc[: -len(".storage.googleapis.com")], - host_url=f"{parsed_url.scheme}://storage.googleapis.com", + provider=CloudProviders.gcs, + bucket_name=parsed_url.netloc[: -len(f".{DEFAULT_GCS_HOST}")], + host_url=f"{parsed_url.scheme}://{DEFAULT_GCS_HOST}", path=parsed_url.path.lstrip("/"), ) elif Config.features.enable_custom_cloud_host: @@ -113,36 +120,41 @@ def from_url(cls, data: str) -> BucketAccessInfo: path = parsed_url.path.lstrip("/") return BucketAccessInfo( - provider=CloudProvider.aws, + provider=CloudProviders.aws, host_url=f"{parsed_url.scheme}://{host}", bucket_name=bucket_name, path=path, ) else: - raise ValueError( - f"{parsed_url.netloc} cloud provider is not supported by CVAT" - ) + raise ValueError(f"{parsed_url.netloc} cloud provider is not supported.") @classmethod - def from_dict(cls, data: Dict) -> BucketAccessInfo: + def _from_dict(cls, data: Dict) -> BucketAccessInfo: for required_field in ( "provider", "bucket_name", ): if required_field not in data: raise ValueError( - f"The {required_field} is required and is not specified in the bucket configuration" + f"The {required_field} is required and is not " + "specified in the bucket configuration" ) - data["provider"] = CloudProvider.from_str(data["provider"].lower()) + provider = CloudProviders.from_str(data["provider"]) + data["provider"] = provider + + if provider == CloudProviders.aws: + access_key = data.pop("access_key", None) + secret_key = data.pop("secret_key", None) + if bool(access_key) ^ bool(secret_key): + raise ValueError("access_key and secret_key can only be used together") - if (access_key := data.pop("access_key", None)) and ( - secret_key := data.pop("secret_key", None) - ): data["credentials"] = S3BucketCredentials(access_key, secret_key) - elif service_account_key := data.pop("service_account_key", None): - data["credentials"] = GCSCredentials(service_account_key) + elif provider == CloudProviders.gcs and ( + service_account_key := data.pop("service_account_key", None) + ): + data["credentials"] = GcsBucketCredentials(service_account_key) return BucketAccessInfo(**data) @@ -151,21 +163,25 @@ def from_storage_config(cls, config: Type[IStorageConfig]) -> BucketAccessInfo: credentials = BucketCredentials.from_storage_config(config) return BucketAccessInfo( - provider=CloudProvider.from_str(config.provider), + provider=CloudProviders.from_str(config.provider), host_url=config.provider_endpoint_url(), bucket_name=config.data_bucket_name, credentials=credentials, ) + @classmethod + def from_bucket_url(cls, bucket_url: manifest.BucketUrl) -> BucketAccessInfo: + return cls._from_dict(bucket_url.dict()) + @classmethod def parse_obj( - cls, data: Union[Dict, str, Type[IStorageConfig]] + cls, data: Union[str, Type[IStorageConfig], manifest.BucketUrl] ) -> BucketAccessInfo: - if isinstance(data, Dict): - return cls.from_dict(data) + if isinstance(data, manifest.BucketUrlBase): + return cls.from_bucket_url(data) elif isinstance(data, str): return cls.from_url(data) - elif issubclass(data, IStorageConfig): + elif isclass(data) and issubclass(data, IStorageConfig): return cls.from_storage_config(data) raise TypeError(f"Unsupported data type ({type(data)}) was provided") diff --git a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py index 138c0565ed..a9f821d174 100644 --- a/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py +++ b/packages/examples/cvat/recording-oracle/src/services/cloud/utils.py @@ -1,20 +1,19 @@ - from typing import Optional from src.services.cloud.client import StorageClient -from src.services.cloud.gcs import GCSClient -from src.services.cloud.s3 import S3Client -from src.services.cloud.types import BucketAccessInfo, CloudProvider +from src.services.cloud.gcs import DEFAULT_GCS_HOST, GcsClient +from src.services.cloud.s3 import DEFAULT_S3_HOST, S3Client +from src.services.cloud.types import BucketAccessInfo, CloudProviders def compose_bucket_url( - bucket_name: str, provider: CloudProvider, *, bucket_host: Optional[str] = None + bucket_name: str, provider: CloudProviders, *, bucket_host: Optional[str] = None ) -> str: match provider: - case CloudProvider.aws: - return f"https://{bucket_name}.{bucket_host or 's3.amazonaws.com'}/" - case CloudProvider.gcs: - return f"https://{bucket_name}.{bucket_host or 'storage.googleapis.com'}/" + case CloudProviders.aws: + return f"https://{bucket_name}.{bucket_host or DEFAULT_S3_HOST}/" + case CloudProviders.gcs: + return f"https://{bucket_name}.{bucket_host or DEFAULT_GCS_HOST}/" def make_client( @@ -25,21 +24,21 @@ def make_client( } match bucket_info.provider: - case CloudProvider.aws: - ClientClass = S3Client + case CloudProviders.aws: + client_type = S3Client + if bucket_info.credentials: client_kwargs["access_key"] = bucket_info.credentials.access_key client_kwargs["secret_key"] = bucket_info.credentials.secret_key if bucket_info.host_url: client_kwargs["endpoint_url"] = bucket_info.host_url + case CloudProviders.gcs: + client_type = GcsClient - case CloudProvider.gcs: - ClientClass = GCSClient if bucket_info.credentials: client_kwargs["service_account_key"] = bucket_info.credentials.service_account_key - case _: raise ValueError(f"Unsupported cloud provider ({bucket_info.provider}) was provided") - return ClientClass(**client_kwargs) + return client_type(**client_kwargs) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index 65c3c02307..87237c1cee 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -37,7 +37,7 @@ def test_file_operations(self): assert client.file_exists(file_name) assert len(client.list_files()) == 1 - file_content = client.download_fileobj(key=file_name) + file_content = client.download_file(key=file_name) assert file_content == data client.remove_file(file_name) @@ -49,10 +49,10 @@ def test_degenerate_file_operations(self): invalid_file = "non-existent-file" with pytest.raises(ClientError): - client.download_fileobj(invalid_file, bucket=invalid_bucket) + client.download_file(invalid_file, bucket=invalid_bucket) with pytest.raises(ClientError): - client.download_fileobj(invalid_file, bucket=self.bucket_name) + client.download_file(invalid_file, bucket=self.bucket_name) with pytest.raises(ClientError): client.create_file(invalid_file, bucket=invalid_bucket) From e6cea8e13f8a34e2471d163934f0694dd7f97291 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 22 Feb 2024 21:26:43 +0200 Subject: [PATCH 48/82] Add job annotation mode param into assignment links --- .../cvat/exchange-oracle/src/services/exchange.py | 1 + .../cvat/exchange-oracle/src/utils/assignments.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/services/exchange.py b/packages/examples/cvat/exchange-oracle/src/services/exchange.py index dcb5b7586b..a3091f1f92 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/services/exchange.py @@ -31,6 +31,7 @@ def serialize_task( assignment_url=compose_assignment_url( task_id=assignment.job.cvat_task_id, job_id=assignment.cvat_job_id, + project=project, ), started_at=assignment.created_at, finishes_at=assignment.expires_at, diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index c505892c23..37b5c39242 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -1,13 +1,18 @@ from urllib.parse import urljoin from src.core.config import Config -from src.core.manifest import TaskManifest +from src.core.manifest import TaskManifest, TaskType from src.core.manifest import parse_manifest as _parse_manifest +from src.models.cvat import Project def parse_manifest(manifest: dict) -> TaskManifest: return _parse_manifest(manifest) -def compose_assignment_url(task_id, job_id) -> str: - return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}") +def compose_assignment_url(task_id: int, job_id: int, *, project: Project) -> str: + query_params = "" + if project.job_type in [TaskType.image_skeletons_from_boxes, TaskType.image_boxes_from_points]: + query_params = "?defaultWorkspace=single_shape" + + return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}{query_params}") From 80d05c98caa3e227d4580756c428f0ce1de59b5b Mon Sep 17 00:00:00 2001 From: Ivan Date: Sun, 25 Feb 2024 22:19:26 +0300 Subject: [PATCH 49/82] Add CD trigger for experimental cvat oracles --- .github/workflows/cd-cvat-exchange-oracle.yaml | 4 ++-- .github/workflows/cd-cvat-recording-oracle.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cd-cvat-exchange-oracle.yaml b/.github/workflows/cd-cvat-exchange-oracle.yaml index 4273e6bb43..a1f2679eec 100644 --- a/.github/workflows/cd-cvat-exchange-oracle.yaml +++ b/.github/workflows/cd-cvat-exchange-oracle.yaml @@ -2,11 +2,11 @@ name: Deploy CVAT Exchange Oracle on: push: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/exchange-oracle/**' pull_request: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/exchange-oracle/**' workflow_dispatch: diff --git a/.github/workflows/cd-cvat-recording-oracle.yaml b/.github/workflows/cd-cvat-recording-oracle.yaml index dbf8d96b21..487c51876b 100644 --- a/.github/workflows/cd-cvat-recording-oracle.yaml +++ b/.github/workflows/cd-cvat-recording-oracle.yaml @@ -2,11 +2,11 @@ name: Deploy CVAT Recording Oracle on: push: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/recording-oracle/**' pull_request: - branches: [ develop, main ] + branches: [ develop, main, cvat-milestone-2 ] paths: - 'packages/examples/cvat/recording-oracle/**' workflow_dispatch: From f21187537009a90798e98d7a2316efab377035af Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 26 Feb 2024 16:04:53 +0200 Subject: [PATCH 50/82] Update code formatting --- .../src/handlers/completed_escrows.py | 2 +- .../src/handlers/job_creation.py | 2 +- .../tests/integration/chain/test_escrow.py | 28 +++++-------------- .../tests/integration/chain/test_kvstore.py | 18 ++++++------ .../integration/endpoints/test_webhook.py | 4 +-- .../services/cloud/test_client_service.py | 11 ++++++-- .../recording-oracle/tests/utils/constants.py | 2 +- .../tests/utils/setup_escrow.py | 4 ++- 8 files changed, 31 insertions(+), 40 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py index 7d6236a961..57bc578680 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py @@ -7,7 +7,6 @@ import src.cvat.api_calls as cvat_api import src.models.cvat as cvat_models -from src.services.cloud.types import BucketAccessInfo import src.services.cloud as cloud_service import src.services.cvat as cvat_service import src.services.webhook as oracle_db_service @@ -25,6 +24,7 @@ postprocess_annotations, prepare_annotation_metafile, ) +from src.services.cloud.types import BucketAccessInfo from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 3804431e3d..cb15a7d750 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -31,8 +31,8 @@ from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME from src.services.cloud import CloudProviders, StorageClient -from src.utils.annotations import ProjectLabels from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url +from src.utils.annotations import ProjectLabels from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index 5cafd8ec8c..2f45811553 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -69,9 +69,7 @@ def test_validate_escrow_without_funds(self): validate_escrow(-1, "", allow_no_funds=False) # should not throw an exception - validate_escrow( - self.network_config.chain_id, self.escrow_address, allow_no_funds=True - ) + validate_escrow(self.network_config.chain_id, self.escrow_address, allow_no_funds=True) def test_validate_escrow_invalid_status(self): escrow_address = create_escrow(self.w3) @@ -85,9 +83,7 @@ def test_validate_escrow_invalid_status(self): with patch("src.chain.escrow.get_escrow") as mock_get_escrow: mock_get_escrow.return_value = self.escrow("Partial", 0.95) - with pytest.raises( - ValueError, match="Escrow is not in any of the accepted states" - ): + with pytest.raises(ValueError, match="Escrow is not in any of the accepted states"): validate_escrow(self.w3.eth.chain_id, escrow_address) def test_get_escrow_manifest(self): @@ -97,9 +93,7 @@ def test_get_escrow_manifest(self): mock_storage.return_value = [json.dumps({"title": "test"}).encode()] mock_get_escrow.return_value = self.escrow() - manifest = get_escrow_manifest( - self.network_config.chain_id, self.escrow_address - ) + manifest = get_escrow_manifest(self.network_config.chain_id, self.escrow_address) self.assertIsInstance(manifest, dict) self.assertIsNotNone(manifest) @@ -111,9 +105,7 @@ def test_store_results(self): self.w3.eth.chain_id, escrow_address, DEFAULT_MANIFEST_URL, DEFAULT_HASH ) self.assertIsNone(results) - intermediate_results_url = get_intermediate_results_url( - self.w3, escrow_address - ) + intermediate_results_url = get_intermediate_results_url(self.w3, escrow_address) self.assertEqual(intermediate_results_url, DEFAULT_MANIFEST_URL) def test_store_results_invalid_url(self): @@ -121,9 +113,7 @@ def test_store_results_invalid_url(self): with patch("src.chain.escrow.get_web3") as mock_function: mock_function.return_value = self.w3 with self.assertRaises(EscrowClientError) as error: - store_results( - self.w3.eth.chain_id, escrow_address, "invalid_url", DEFAULT_HASH - ) + store_results(self.w3.eth.chain_id, escrow_address, "invalid_url", DEFAULT_HASH) self.assertEqual(f"Invalid URL: invalid_url", str(error.exception)) def test_store_results_invalid_hash(self): @@ -143,9 +133,7 @@ def test_get_reputation_oracle_address(self): mock_escrow = MagicMock() mock_escrow.reputation_oracle = REPUTATION_ORACLE_ADDRESS mock_get_escrow.return_value = mock_escrow - address = get_reputation_oracle_address( - self.w3.eth.chain_id, escrow_address - ) + address = get_reputation_oracle_address(self.w3.eth.chain_id, escrow_address) self.assertIsInstance(address, str) self.assertIsNotNone(address) @@ -157,6 +145,4 @@ def test_get_reputation_oracle_address_invalid_address(self): def test_get_reputation_oracle_address_invalid_chain_id(self): with pytest.raises(Exception, match="Can't find escrow"): - get_reputation_oracle_address( - 1, "0x1234567890123456789012345678901234567890" - ) + get_reputation_oracle_address(1, "0x1234567890123456789012345678901234567890") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py index dc8ff0bb0a..ec88c3f3b7 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_kvstore.py @@ -9,7 +9,11 @@ from src.chain.kvstore import get_reputation_oracle_url, get_role_by_address -from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, DEFAULT_MANIFEST_URL, REPUTATION_ORACLE_ADDRESS +from tests.utils.constants import ( + DEFAULT_GAS_PAYER_PRIV, + DEFAULT_MANIFEST_URL, + REPUTATION_ORACLE_ADDRESS, +) from tests.utils.setup_escrow import create_escrow from tests.utils.setup_kvstore import store_kvstore_value @@ -41,9 +45,7 @@ def test_get_reputation_oracle_url(self): mock_get_escrow.return_value = mock_escrow mock_leader.return_value = MagicMock(webhook_url=DEFAULT_MANIFEST_URL) - reputation_url = get_reputation_oracle_url( - self.w3.eth.chain_id, escrow_address - ) + reputation_url = get_reputation_oracle_url(self.w3.eth.chain_id, escrow_address) self.assertEqual(reputation_url, DEFAULT_MANIFEST_URL) def test_get_reputation_oracle_url_invalid_escrow(self): @@ -75,9 +77,7 @@ def test_get_role_by_address(self): store_kvstore_value("role", "Reputation Oracle") with patch("src.chain.kvstore.get_web3") as mock_function: mock_function.return_value = self.w3 - reputation_url = get_role_by_address( - self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS - ) + reputation_url = get_role_by_address(self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS) self.assertEqual(reputation_url, "Reputation Oracle") def test_get_role_by_address_invalid_escrow(self): @@ -92,7 +92,5 @@ def test_get_role_by_address_invalid_address(self): store_kvstore_value("role", "") with patch("src.chain.kvstore.get_web3") as mock_function: mock_function.return_value = self.w3 - reputation_url = get_role_by_address( - self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS - ) + reputation_url = get_role_by_address(self.w3.eth.chain_id, REPUTATION_ORACLE_ADDRESS) self.assertEqual(reputation_url, "") diff --git a/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py b/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py index 22f10e617c..fac373dbd9 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py @@ -49,9 +49,7 @@ def test_receive_oracle_webhook_client(self, mock_get_escrow): assert response.status_code == 200 response_body = response.json() - webhook = ( - self.session.query(Webhook).where(Webhook.id == response_body["id"]).one() - ) + webhook = self.session.query(Webhook).where(Webhook.id == response_body["id"]).one() assert webhook is not None assert webhook.escrow_address == escrow_address assert webhook.chain_id == chain_id diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py index 87237c1cee..75cf94eec5 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/cloud/test_client_service.py @@ -25,7 +25,12 @@ def tearDown(self): self.client.delete_bucket(Bucket=self.bucket_name) def test_file_operations(self): - client = S3Client(endpoint_url=self.url, bucket=self.bucket_name, access_key=self.access_key, secret_key=self.secret) + client = S3Client( + endpoint_url=self.url, + bucket=self.bucket_name, + access_key=self.access_key, + secret_key=self.secret, + ) assert len(client.list_files()) == 0 @@ -65,7 +70,9 @@ def test_degenerate_file_operations(self): def test_degenerate_client(self): with pytest.raises(EndpointConnectionError): invalid_client = S3Client( - endpoint_url="http://not.an.url:1234", access_key=self.access_key, secret_key=self.secret + endpoint_url="http://not.an.url:1234", + access_key=self.access_key, + secret_key=self.secret, ) invalid_client.create_file("test.txt", bucket=self.bucket_name) diff --git a/packages/examples/cvat/recording-oracle/tests/utils/constants.py b/packages/examples/cvat/recording-oracle/tests/utils/constants.py index e5ff716440..900ab8574d 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/constants.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/constants.py @@ -4,7 +4,7 @@ RECORDING_ORACLE_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" RECORDING_ORACLE_FEE = 10 -REPUTATION_ORACLE_WEBHOOK_URL = 'http://localhost:5001/webhook/cvat' +REPUTATION_ORACLE_WEBHOOK_URL = "http://localhost:5001/webhook/cvat" REPUTATION_ORACLE_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" REPUTATION_ORACLE_PRIV = "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" REPUTATION_ORACLE_FEE = 10 diff --git a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py index 17af89c712..afc161a1ed 100644 --- a/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/utils/setup_escrow.py @@ -50,7 +50,9 @@ def fund_escrow(web3: Web3, escrow_address: str): def bulk_payout(web3: Web3, escrow_address: str, recipient: str, amount: Decimal): escrow_client = EscrowClient(web3) - escrow_client.bulk_payout(escrow_address, [recipient], [amount], DEFAULT_MANIFEST_URL, DEFAULT_HASH, 1) + escrow_client.bulk_payout( + escrow_address, [recipient], [amount], DEFAULT_MANIFEST_URL, DEFAULT_HASH, 1 + ) def get_intermediate_results_url(web3: Web3, escrow_address: str): From 5dc0c810ba8fe87bc8fbe89d9506678d67984ebf Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 26 Feb 2024 16:09:03 +0200 Subject: [PATCH 51/82] Fix test --- .../cron/state_trackers/test_track_completed_escrows.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py index 330bfa289e..bc349c0b63 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py @@ -250,12 +250,16 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): patch("src.handlers.completed_escrows.validate_escrow"), patch("src.handlers.completed_escrows.cvat_api"), patch("src.handlers.completed_escrows.cvat_api.get_job_annotations") as mock_annotations, - patch("src.handlers.completed_escrows.cloud_client") as mock_make_client, + patch("src.handlers.completed_escrows.cloud_service") as mock_cloud_service, ): manifest = json.load(data) mock_get_manifest.return_value = manifest + mock_create_file = Mock() - mock_make_client.return_value.create_file = mock_create_file + mock_storage_client = Mock() + mock_storage_client.create_file = mock_create_file + mock_cloud_service.make_client = Mock(return_value=mock_storage_client) + mock_annotations.side_effect = Exception("Connection error") track_completed_escrows() From 4ad0d17e75ade113830f07580d417450bcdd709c Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 26 Feb 2024 16:13:56 +0200 Subject: [PATCH 52/82] Format code --- .../cron/state_trackers/test_track_completed_escrows.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py index bc349c0b63..f6ba463e14 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py @@ -249,7 +249,9 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): patch("src.handlers.completed_escrows.get_escrow_manifest") as mock_get_manifest, patch("src.handlers.completed_escrows.validate_escrow"), patch("src.handlers.completed_escrows.cvat_api"), - patch("src.handlers.completed_escrows.cvat_api.get_job_annotations") as mock_annotations, + patch( + "src.handlers.completed_escrows.cvat_api.get_job_annotations" + ) as mock_annotations, patch("src.handlers.completed_escrows.cloud_service") as mock_cloud_service, ): manifest = json.load(data) From 7e7f012ce32b7a1273c8281a71d6bdece1f9c787 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 26 Feb 2024 16:52:00 +0200 Subject: [PATCH 53/82] Fix bucket uses --- .../src/handlers/job_creation.py | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index cb15a7d750..cb430cdc82 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -991,20 +991,12 @@ def _download_input_data(self): gt_storage_client = self._make_cloud_storage_client(gt_bucket) boxes_storage_client = self._make_cloud_storage_client(boxes_bucket) - data_filenames = data_storage_client.list_files( - prefix=data_bucket.path, - ) + data_filenames = data_storage_client.list_files(prefix=data_bucket.path) self.input_filenames = filter_image_files(data_filenames) - self.input_gt_data = gt_storage_client.download_file( - gt_bucket.bucket_name, - gt_bucket.path, - ) + self.input_gt_data = gt_storage_client.download_file(gt_bucket.path) - self.input_boxes_data = boxes_storage_client.download_file( - boxes_bucket.bucket_name, - boxes_bucket.path, - ) + self.input_boxes_data = boxes_storage_client.download_file(boxes_bucket.path) def _parse_dataset(self, annotation_file_data: bytes, dataset_format: str) -> dm.Dataset: temp_dir = self.exit_stack.enter_context(TemporaryDirectory()) @@ -1474,7 +1466,6 @@ def _upload_task_meta(self): bucket_name = self.oracle_data_bucket.bucket_name for file_data, filename in file_list: storage_client.create_file( - bucket_name, compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), file_data, ) @@ -1527,7 +1518,6 @@ def _extract_and_upload_rois(self): assert self.roi_filenames is not _unset assert self.roi_infos is not _unset - # TODO: optimize downloading, this implementation won't work for big datasets src_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) src_prefix = "" dst_bucket = self.oracle_data_bucket @@ -1559,9 +1549,7 @@ def _extract_and_upload_rois(self): if not image_roi_infos: continue - image_bytes = src_client.download_file( - src_bucket.bucket_name, os.path.join(src_prefix, filename) - ) + image_bytes = src_client.download_file(os.path.join(src_prefix, filename)) image_pixels = decode_image(image_bytes) sample = filename_to_sample[filename] @@ -1583,10 +1571,7 @@ def _extract_and_upload_rois(self): roi_bytes = encode_image(roi_pixels, os.path.splitext(filename)[-1]) dst_client.create_file( - dst_bucket.bucket_name, - filename=compose_data_bucket_filename( - self.escrow_address, self.chain_id, filename - ), + compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), data=roi_bytes, ) From 5b2909cb42bdd95dd64f2351c67cbb36b52f28e0 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 26 Feb 2024 17:29:40 +0200 Subject: [PATCH 54/82] Fix result sending --- .../cvat/exchange-oracle/src/handlers/completed_escrows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py index 57bc578680..5ad088a702 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py @@ -311,8 +311,8 @@ def _process_skeletons_from_boxes_escrows(self): existing_storage_files = set( storage_client.list_files( prefix=compose_results_bucket_filename( - project.escrow_address, - project.chain_id, + escrow_address, + chain_id, "", ), ) From 345cec874f29cc0d3df57a7fd5d6447089e3ddcd Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 27 Feb 2024 10:56:02 +0200 Subject: [PATCH 55/82] Remove extra changes --- .../src/handlers/process_intermediate_results.py | 6 ++---- .../src/validation/dataset_comparison.py | 12 +++--------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 32bef2d16b..060765aebd 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -90,7 +90,6 @@ def _require_field(self, field: Optional[T]) -> T: assert field is not None return field - def _parse_gt(self): tempdir = self._require_field(self._temp_dir) manifest = self._require_field(self.manifest) @@ -132,9 +131,8 @@ def _validate_jobs(self): format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], ) - job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) - job_results[job_cvat_id] = job_mean_accuracy - + job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) + job_results[job_cvat_id] = job_mean_accuracy if job_mean_accuracy < manifest.validation.min_quality: rejected_job_ids.append(job_cvat_id) diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index c04b4e2939..befa5ba9f5 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -54,7 +54,7 @@ def clear_cache(self): class DatasetComparator(metaclass=ABCMeta): min_similarity_threshold: float - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> Tuple[float, Set[str]]: + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: dataset_similarities = [] dataset_total_anns_to_compare = 0 @@ -75,18 +75,12 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> Tuple[float zip(itertools.repeat(None), matching_result.b_extra), ): sim = similarity_fn(gt_ann, ds_ann) if gt_ann and ds_ann else 0 + sample_similarities.append(sim) sample_total_anns_to_compare += (gt_ann is not None) + (ds_ann is not None) dataset_similarities.extend(sample_similarities) dataset_total_anns_to_compare += sample_total_anns_to_compare - sample_accuracy = 0 - if sample_total_anns_to_compare: - sample_accuracy = 2 * np.sum(sample_similarities) / sample_total_anns_to_compare - all_similarities.append(sim) - - total_anns_to_compare += (gt_ann is not None) + (ds_ann is not None) - dataset_accuracy = 0 if dataset_total_anns_to_compare: dataset_accuracy = 2 * np.sum(dataset_similarities) / dataset_total_anns_to_compare @@ -170,7 +164,7 @@ def __init__(self, *args, **kwargs): # TODO: find better strategy for sigma estimation self.oks_sigma = 0.1 # average value for COCO points - def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> Tuple[float, Set[str]]: + def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: self._categories = gt_dataset.categories() return super().compare(gt_dataset, ds_dataset) From 80a0d663d7e04ae447c94a26e67f542bedba7ed5 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 27 Feb 2024 11:15:04 +0200 Subject: [PATCH 56/82] Fix extra code --- .../handlers/process_intermediate_results.py | 36 ++++++++++++------- .../src/validation/dataset_comparison.py | 2 +- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 060765aebd..b514635cc8 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -3,7 +3,7 @@ import os from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union +from typing import Dict, List, Optional, Sequence, Tuple, Type, TypeVar, Union import datumaro as dm import numpy as np @@ -75,13 +75,21 @@ class ValidationFailure: class _TaskValidator: - def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): + def __init__( + self, + escrow_address: str, + chain_id: int, + manifest: TaskManifest, + *, + job_annotations: Dict[int, io.RawIOBase], + merged_annotations: Optional[io.RawIOBase] = None, + ): self.escrow_address = escrow_address self.chain_id = chain_id self.manifest = manifest - self.job_annotations: Optional[Dict[int, io.IOBase]] = None - self.merged_annotations: Optional[io.IOBase] = None + self.job_annotations: Optional[Dict[int, io.IOBase]] = job_annotations + self.merged_annotations: Optional[io.IOBase] = merged_annotations self._temp_dir: Optional[Path] = None self._gt_dataset: Optional[dm.Dataset] = None @@ -226,8 +234,8 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: class _BoxesFromPointsValidator(_TaskValidator): - def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): - super().__init__(escrow_address, chain_id, manifest) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) ( boxes_to_points_mapping, @@ -426,8 +434,8 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: class _SkeletonsFromBoxesValidator(_TaskValidator): - def __init__(self, escrow_address: str, chain_id: int, manifest: TaskManifest): - super().__init__(escrow_address, chain_id, manifest) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) ( roi_filenames, @@ -699,7 +707,6 @@ def process_intermediate_results( chain_id: int, meta: AnnotationMeta, job_annotations: Dict[int, io.RawIOBase], - gt_annotations: io.RawIOBase, merged_annotations: io.RawIOBase, manifest: TaskManifest, logger: logging.Logger, @@ -715,10 +722,13 @@ def process_intermediate_results( else: raise Exception(f"Unknown task type {task_type}") - validator = validator_type(escrow_address=escrow_address, chain_id=chain_id, manifest=manifest) - validator.gt_annotations = gt_annotations - validator.job_annotations = job_annotations - validator.merged_annotations = merged_annotations + validator = validator_type( + escrow_address=escrow_address, + chain_id=chain_id, + manifest=manifest, + job_annotations=job_annotations, + merged_annotations=merged_annotations, + ) job_results, rejected_job_ids, updated_merged_dataset_archive = validator.validate() if logger.isEnabledFor(logging.DEBUG): diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index befa5ba9f5..787b080962 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -67,7 +67,7 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: matching_result, similarity_fn = self.compare_sample_annotations(gt_sample, ds_sample) sample_similarities = [] - sample_total_anns_to_compare = [] + sample_total_anns_to_compare = 0 for gt_ann, ds_ann in itertools.chain( matching_result.matches, matching_result.mispred, From 4e86b7fc50a6daf25a896a382300c0243d9eed25 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 27 Feb 2024 11:27:52 +0200 Subject: [PATCH 57/82] Remove unused code --- .../recording-oracle/src/schemas/agreement.py | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 packages/examples/cvat/recording-oracle/src/schemas/agreement.py diff --git a/packages/examples/cvat/recording-oracle/src/schemas/agreement.py b/packages/examples/cvat/recording-oracle/src/schemas/agreement.py deleted file mode 100644 index 9088ddf120..0000000000 --- a/packages/examples/cvat/recording-oracle/src/schemas/agreement.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Generic, Mapping, Optional, Sequence, TypeVar - -from pydantic import BaseModel - -T = TypeVar("T") - - -class ImageLabelBinaryFinalResult(BaseModel): - url: str - label: str - label_counts: Mapping[str, int] - score: float - - -class AgreementEstimate(BaseModel): - score: float - interval: Optional[tuple[float, float]] - alpha: Optional[float] - - -class ResultDataset(BaseModel, Generic[T]): - dataset_scores: Mapping[str, AgreementEstimate] - data_points: Sequence[T] - - -class WorkerPerformanceResult(BaseModel): - worker_id: str - consensus_annotations: int - total_annotations: int - score: float - - -class ImageLabelBinaryJobResults(BaseModel): - dataset: ResultDataset[ImageLabelBinaryFinalResult] - worker_performance: Sequence[WorkerPerformanceResult] From 349ebaa307215b784fb63631c238e8e1f43a52af Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 28 Feb 2024 11:08:41 +0200 Subject: [PATCH 58/82] Align enum naming convention --- .../cvat/exchange-oracle/src/core/manifest.py | 4 +- .../exchange-oracle/src/core/oracle_events.py | 30 +++---- .../cvat/exchange-oracle/src/core/types.py | 16 ++-- .../crons/process_job_launcher_webhooks.py | 6 +- .../process_recording_oracle_webhooks.py | 10 +-- .../src/crons/state_trackers.py | 8 +- .../src/handlers/completed_escrows.py | 6 +- .../src/handlers/cvat_events.py | 6 +- .../src/handlers/job_creation.py | 48 +++++----- .../src/handlers/job_export.py | 24 ++--- .../cvat/exchange-oracle/src/models/cvat.py | 16 ++-- .../exchange-oracle/src/schemas/exchange.py | 6 +- .../exchange-oracle/src/schemas/webhook.py | 4 +- .../cvat/exchange-oracle/src/services/cvat.py | 30 +++---- .../exchange-oracle/src/services/exchange.py | 6 +- .../exchange-oracle/src/services/webhook.py | 14 +-- .../exchange-oracle/src/utils/assignments.py | 7 +- .../tests/api/test_cvat_webhook_api.py | 6 +- .../tests/api/test_exchange_api.py | 4 +- .../tests/api/test_webhook_api.py | 4 +- .../state_trackers/test_track_assignments.py | 14 +-- .../test_track_completed_escrows.py | 24 ++--- .../test_track_completed_projects.py | 12 +-- .../test_track_completed_tasks.py | 14 +-- .../test_track_task_creation.py | 2 +- .../test_process_job_launcher_webhooks.py | 52 +++++------ .../test_process_recording_oracle_webhooks.py | 46 +++++----- .../tests/integration/services/test_cvat.py | 88 +++++++++---------- .../integration/services/test_exchange.py | 8 +- .../integration/services/test_webhook.py | 44 +++++----- .../exchange-oracle/tests/utils/db_helper.py | 6 +- .../recording-oracle/src/core/manifest.py | 4 +- .../src/core/oracle_events.py | 18 ++-- .../cvat/recording-oracle/src/core/types.py | 6 +- .../crons/process_exchange_oracle_webhooks.py | 4 +- .../handlers/process_intermediate_results.py | 48 +++++----- .../recording-oracle/src/schemas/webhook.py | 4 +- .../recording-oracle/src/services/webhook.py | 14 +-- .../test_process_exchange_oracle_webhooks.py | 8 +- ...test_process_reputation_oracle_webhooks.py | 10 +-- .../integration/endpoints/test_webhook.py | 4 +- .../services/test_webhook_service.py | 4 +- 42 files changed, 346 insertions(+), 343 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 7c3afab308..a3dbef6499 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -5,7 +5,7 @@ from pydantic import AnyUrl, BaseModel, Field, root_validator from src.core.config import Config -from src.core.types import TaskType +from src.core.types import TaskTypes from src.utils.enums import BetterEnumMeta @@ -119,7 +119,7 @@ def validate_type(cls, values: dict) -> dict: class AnnotationInfo(BaseModel): - type: TaskType + type: TaskTypes labels: list[LabelInfo] = Field(min_items=1) "Label declarations with accepted annotation types" diff --git a/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py b/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py index 5ca8eb1c17..334fd2ee80 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py +++ b/packages/examples/cvat/exchange-oracle/src/core/oracle_events.py @@ -3,16 +3,16 @@ from pydantic import BaseModel from src.core.types import ( - ExchangeOracleEventType, - JobLauncherEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, OracleWebhookTypes, - RecordingOracleEventType, + RecordingOracleEventTypes, ) EventTypeTag = Union[ - ExchangeOracleEventType, - JobLauncherEventType, - RecordingOracleEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, + RecordingOracleEventTypes, ] @@ -49,12 +49,12 @@ class ExchangeOracleEvent_TaskFinished(OracleEvent): _event_type_map = { - JobLauncherEventType.escrow_created: JobLauncherEvent_EscrowCreated, - JobLauncherEventType.escrow_canceled: JobLauncherEvent_EscrowCanceled, - RecordingOracleEventType.task_completed: RecordingOracleEvent_TaskCompleted, - RecordingOracleEventType.task_rejected: RecordingOracleEvent_TaskRejected, - ExchangeOracleEventType.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, - ExchangeOracleEventType.task_finished: ExchangeOracleEvent_TaskFinished, + JobLauncherEventTypes.escrow_created: JobLauncherEvent_EscrowCreated, + JobLauncherEventTypes.escrow_canceled: JobLauncherEvent_EscrowCanceled, + RecordingOracleEventTypes.task_completed: RecordingOracleEvent_TaskCompleted, + RecordingOracleEventTypes.task_rejected: RecordingOracleEvent_TaskRejected, + ExchangeOracleEventTypes.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, + ExchangeOracleEventTypes.task_finished: ExchangeOracleEvent_TaskFinished, } @@ -84,9 +84,9 @@ def parse_event( event_data: Optional[dict] = None, ) -> OracleEvent: sender_events_mapping = { - OracleWebhookTypes.job_launcher: JobLauncherEventType, - OracleWebhookTypes.recording_oracle: RecordingOracleEventType, - OracleWebhookTypes.exchange_oracle: ExchangeOracleEventType, + OracleWebhookTypes.job_launcher: JobLauncherEventTypes, + OracleWebhookTypes.recording_oracle: RecordingOracleEventTypes, + OracleWebhookTypes.exchange_oracle: ExchangeOracleEventTypes, } sender_events = sender_events_mapping.get(sender) diff --git a/packages/examples/cvat/exchange-oracle/src/core/types.py b/packages/examples/cvat/exchange-oracle/src/core/types.py index 92a79a467c..eda4e80b00 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/types.py +++ b/packages/examples/cvat/exchange-oracle/src/core/types.py @@ -24,7 +24,7 @@ class ProjectStatuses(str, Enum, metaclass=BetterEnumMeta): recorded = "recorded" -class TaskStatus(str, Enum, metaclass=BetterEnumMeta): +class TaskStatuses(str, Enum, metaclass=BetterEnumMeta): annotation = "annotation" completed = "completed" @@ -36,7 +36,7 @@ class JobStatuses(str, Enum, metaclass=BetterEnumMeta): completed = "completed" -class TaskType(str, Enum, metaclass=BetterEnumMeta): +class TaskTypes(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" @@ -44,7 +44,7 @@ class TaskType(str, Enum, metaclass=BetterEnumMeta): image_skeletons_from_boxes = "IMAGE_SKELETONS_FROM_BOXES" -class CvatLabelType(str, Enum, metaclass=BetterEnumMeta): +class CvatLabelTypes(str, Enum, metaclass=BetterEnumMeta): tag = "tag" points = "points" rectangle = "rectangle" @@ -56,17 +56,17 @@ class OracleWebhookTypes(str, Enum, metaclass=BetterEnumMeta): recording_oracle = "recording_oracle" -class ExchangeOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class ExchangeOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_creation_failed = "task_creation_failed" task_finished = "task_finished" -class JobLauncherEventType(str, Enum, metaclass=BetterEnumMeta): +class JobLauncherEventTypes(str, Enum, metaclass=BetterEnumMeta): escrow_created = "escrow_created" escrow_canceled = "escrow_canceled" -class RecordingOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class RecordingOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_completed = "task_completed" task_rejected = "task_rejected" @@ -77,11 +77,11 @@ class OracleWebhookStatuses(str, Enum, metaclass=BetterEnumMeta): failed = "failed" -class PlatformType(str, Enum, metaclass=BetterEnumMeta): +class PlatformTypes(str, Enum, metaclass=BetterEnumMeta): CVAT = "cvat" -class AssignmentStatus(str, Enum, metaclass=BetterEnumMeta): +class AssignmentStatuses(str, Enum, metaclass=BetterEnumMeta): created = "created" completed = "completed" expired = "expired" diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py index ecde495883..ee5d15121c 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py @@ -10,7 +10,7 @@ from src.chain.kvstore import get_job_launcher_url from src.core.config import Config, CronConfig from src.core.oracle_events import ExchangeOracleEvent_TaskCreationFailed -from src.core.types import JobLauncherEventType, OracleWebhookTypes, ProjectStatuses +from src.core.types import JobLauncherEventTypes, OracleWebhookTypes, ProjectStatuses from src.db import SessionLocal from src.db.utils import ForUpdateParams from src.log import ROOT_LOGGER_NAME @@ -67,7 +67,7 @@ def handle_job_launcher_event( assert webhook.type == OracleWebhookTypes.job_launcher match webhook.event_type: - case JobLauncherEventType.escrow_created: + case JobLauncherEventTypes.escrow_created: try: validate_escrow( webhook.chain_id, @@ -109,7 +109,7 @@ def handle_job_launcher_event( raise - case JobLauncherEventType.escrow_canceled: + case JobLauncherEventTypes.escrow_canceled: try: validate_escrow( webhook.chain_id, diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py index cfedb705fe..e44a278f88 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_recording_oracle_webhooks.py @@ -13,8 +13,8 @@ JobStatuses, OracleWebhookTypes, ProjectStatuses, - RecordingOracleEventType, - TaskStatus, + RecordingOracleEventTypes, + TaskStatuses, ) from src.db import SessionLocal from src.db.utils import ForUpdateParams @@ -61,7 +61,7 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg assert webhook.type == OracleWebhookTypes.recording_oracle match webhook.event_type: - case RecordingOracleEventType.task_completed: + case RecordingOracleEventTypes.task_completed: chunk_size = CronConfig.accepted_projects_chunk_size project_ids = cvat_db_service.get_project_cvat_ids_by_escrow_address( db_session, webhook.escrow_address @@ -102,7 +102,7 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg cvat_db_service.update_project_status(db_session, project.id, new_status) - case RecordingOracleEventType.task_rejected: + case RecordingOracleEventTypes.task_rejected: event = RecordingOracleEvent_TaskRejected.parse_obj(webhook.event_data) rejected_jobs = cvat_db_service.get_jobs_by_cvat_id(db_session, event.rejected_job_ids) @@ -137,7 +137,7 @@ def handle_recording_oracle_event(webhook: Webhook, *, db_session: Session, logg for task_id in tasks_to_update: cvat_db_service.update_task_status( - db_session, task_id, TaskStatus.annotation + db_session, task_id, TaskStatuses.annotation ) cvat_db_service.update_project_status( diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 5d12e94c10..069248c7ca 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -6,7 +6,7 @@ import src.services.webhook as oracle_db_service from src.core.config import CronConfig from src.core.oracle_events import ExchangeOracleEvent_TaskCreationFailed -from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatus +from src.core.types import JobStatuses, OracleWebhookTypes, ProjectStatuses, TaskStatuses from src.db import SessionLocal from src.db.utils import ForUpdateParams from src.handlers.completed_escrows import handle_completed_escrows @@ -40,7 +40,7 @@ def track_completed_projects() -> None: for project in projects: tasks = cvat_service.get_tasks_by_cvat_project_id(session, project.cvat_id) - if tasks and all(task.status == TaskStatus.completed for task in tasks): + if tasks and all(task.status == TaskStatuses.completed for task in tasks): cvat_service.update_project_status( session, project.id, ProjectStatuses.completed ) @@ -72,7 +72,7 @@ def track_completed_tasks() -> None: logger.debug("Starting cron job") with SessionLocal.begin() as session: tasks = cvat_service.get_tasks_by_status( - session, TaskStatus.annotation, for_update=ForUpdateParams(skip_locked=True) + session, TaskStatuses.annotation, for_update=ForUpdateParams(skip_locked=True) ) completed_task_ids = [] @@ -80,7 +80,7 @@ def track_completed_tasks() -> None: for task in tasks: jobs = cvat_service.get_jobs_by_cvat_task_id(session, task.cvat_id) if jobs and all(job.status == JobStatuses.completed for job in jobs): - cvat_service.update_task_status(session, task.id, TaskStatus.completed) + cvat_service.update_task_status(session, task.id, TaskStatuses.completed) completed_task_ids.append(task.cvat_id) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py index 5ad088a702..fda29d2162 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/completed_escrows.py @@ -15,7 +15,7 @@ from src.core.config import CronConfig, StorageConfig from src.core.oracle_events import ExchangeOracleEvent_TaskFinished from src.core.storage import compose_results_bucket_filename -from src.core.types import OracleWebhookTypes, ProjectStatuses, TaskType +from src.core.types import OracleWebhookTypes, ProjectStatuses, TaskTypes from src.db import SessionLocal from src.db.utils import ForUpdateParams from src.handlers.job_export import ( @@ -44,7 +44,7 @@ def __init__(self, logger: Optional[logging.Logger]) -> None: def _process_plain_escrows(self): logger = self.logger - plain_task_types = [t for t in TaskType if not t == TaskType.image_skeletons_from_boxes] + plain_task_types = [t for t in TaskTypes if not t == TaskTypes.image_skeletons_from_boxes] with SessionLocal.begin() as session: completed_projects = cvat_service.get_projects_by_status( session, @@ -189,7 +189,7 @@ def _process_skeletons_from_boxes_escrows(self): completed_projects = cvat_service.get_projects_by_status( session, ProjectStatuses.completed, - included_types=[TaskType.image_skeletons_from_boxes], + included_types=[TaskTypes.image_skeletons_from_boxes], limit=CronConfig.track_completed_escrows_chunk_size, ) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py index 5f0e8f3851..39a6b972d9 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/cvat_events.py @@ -5,7 +5,7 @@ import src.cvat.api_calls as cvat_api import src.models.cvat as models import src.services.cvat as cvat_service -from src.core.types import AssignmentStatus, CvatEventTypes, JobStatuses +from src.core.types import AssignmentStatuses, CvatEventTypes, JobStatuses from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME from src.utils.logging import get_function_logger @@ -60,7 +60,7 @@ def handle_update_job_event(payload: dict) -> None: "Can't find a matching assignment, ignoring the update" ) elif matching_assignment.is_finished: - if matching_assignment.status == AssignmentStatus.created: + if matching_assignment.status == AssignmentStatuses.created: logger.warning( f"Received job #{job.cvat_id} status update: {new_status.value}. " "Assignment is expired, rejecting the update" @@ -78,7 +78,7 @@ def handle_update_job_event(payload: dict) -> None: elif ( new_status == JobStatuses.completed and matching_assignment.id == latest_assignment.id - and matching_assignment.status == AssignmentStatus.created + and matching_assignment.status == AssignmentStatuses.created ): logger.info( f"Received job #{job.cvat_id} status update: {new_status.value}. " diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index a121997af4..4e3867ae5f 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -27,7 +27,7 @@ from src.core.config import Config from src.core.manifest import TaskManifest from src.core.storage import compose_data_bucket_filename -from src.core.types import CvatLabelType, TaskStatus, TaskType +from src.core.types import CvatLabelTypes, TaskStatuses, TaskTypes from src.db import SessionLocal from src.log import ROOT_LOGGER_NAME from src.services.cloud import CloudProviders, StorageClient @@ -39,28 +39,28 @@ module_logger = f"{ROOT_LOGGER_NAME}.cron.cvat" LABEL_TYPE_MAPPING = { - TaskType.image_label_binary: CvatLabelType.tag, - TaskType.image_points: CvatLabelType.points, - TaskType.image_boxes: CvatLabelType.rectangle, - TaskType.image_boxes_from_points: CvatLabelType.rectangle, - TaskType.image_skeletons_from_boxes: CvatLabelType.points, + TaskTypes.image_label_binary: CvatLabelTypes.tag, + TaskTypes.image_points: CvatLabelTypes.points, + TaskTypes.image_boxes: CvatLabelTypes.rectangle, + TaskTypes.image_boxes_from_points: CvatLabelTypes.rectangle, + TaskTypes.image_skeletons_from_boxes: CvatLabelTypes.points, } DM_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_person_keypoints", - TaskType.image_boxes: "coco_instances", - TaskType.image_boxes_from_points: "coco_instances", - TaskType.image_skeletons_from_boxes: "coco_person_keypoints", + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_person_keypoints", + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", } DM_GT_DATASET_FORMAT_MAPPING = { # GT uses the same format both for boxes and points - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_instances", - TaskType.image_boxes: "coco_instances", - TaskType.image_boxes_from_points: "coco_instances", - TaskType.image_skeletons_from_boxes: "coco_person_keypoints", + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_instances", + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", } @@ -975,7 +975,7 @@ def _create_on_cvat(self): task = cvat_api.create_task(project.id, self.escrow_address) with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + db_service.create_task(session, task.id, project.id, TaskStatuses[task.status]) # Actual task creation in CVAT takes some time, so it's done in an async process. # The task will be created in DB once 'update:task' or 'update:job' webhook is received. @@ -1896,7 +1896,7 @@ def _create_on_cvat(self): with SessionLocal.begin() as session: db_service.create_task( - session, task.id, project.id, TaskStatus[task.status] + session, task.id, project.id, TaskStatuses[task.status] ) # Actual task creation in CVAT takes some time, so it's done in an async process. @@ -2038,9 +2038,9 @@ def create_task(escrow_address: str, chain_id: int) -> None: manifest = parse_manifest(get_escrow_manifest(chain_id, escrow_address)) if manifest.annotation.type in [ - TaskType.image_boxes, - TaskType.image_points, - TaskType.image_label_binary, + TaskTypes.image_boxes, + TaskTypes.image_points, + TaskTypes.image_label_binary, ]: data_bucket = BucketAccessInfo.parse_obj(manifest.data.data_url) gt_bucket = BucketAccessInfo.parse_obj(manifest.validation.gt_url) @@ -2098,7 +2098,7 @@ def create_task(escrow_address: str, chain_id: int) -> None: task = cvat_api.create_task(project.id, escrow_address) with SessionLocal.begin() as session: - db_service.create_task(session, task.id, project.id, TaskStatus[task.status]) + db_service.create_task(session, task.id, project.id, TaskStatuses[task.status]) # Actual task creation in CVAT takes some time, so it's done in an async process. # The task will be created in DB once 'update:task' or 'update:job' webhook is received. @@ -2112,12 +2112,12 @@ def create_task(escrow_address: str, chain_id: int) -> None: with SessionLocal.begin() as session: db_service.create_data_upload(session, cvat_task_id=task.id) - elif manifest.annotation.type in [TaskType.image_boxes_from_points]: + elif manifest.annotation.type in [TaskTypes.image_boxes_from_points]: with BoxesFromPointsTaskBuilder(manifest, escrow_address, chain_id) as task_builder: task_builder.set_logger(logger) task_builder.build() - elif manifest.annotation.type in [TaskType.image_skeletons_from_boxes]: + elif manifest.annotation.type in [TaskTypes.image_skeletons_from_boxes]: with SkeletonsFromBoxesTaskBuilder(manifest, escrow_address, chain_id) as task_builder: task_builder.set_logger(logger) task_builder.build() diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py index 08b851dae5..1f494b8cc5 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_export.py @@ -15,7 +15,7 @@ from src.core.config import Config from src.core.manifest import TaskManifest from src.core.storage import compose_data_bucket_filename -from src.core.types import TaskType +from src.core.types import TaskTypes from src.handlers.job_creation import DM_DATASET_FORMAT_MAPPING from src.models.cvat import Image, Job from src.services.cloud import make_client as make_cloud_client @@ -23,11 +23,11 @@ from src.utils.zip_archive import extract_zip_archive, write_dir_to_zip_archive CVAT_EXPORT_FORMAT_MAPPING = { - TaskType.image_label_binary: "CVAT for images 1.1", - TaskType.image_points: "CVAT for images 1.1", - TaskType.image_boxes: "COCO 1.0", - TaskType.image_boxes_from_points: "COCO 1.0", - TaskType.image_skeletons_from_boxes: "CVAT for images 1.1", + TaskTypes.image_label_binary: "CVAT for images 1.1", + TaskTypes.image_points: "CVAT for images 1.1", + TaskTypes.image_boxes: "COCO 1.0", + TaskTypes.image_boxes_from_points: "COCO 1.0", + TaskTypes.image_skeletons_from_boxes: "CVAT for images 1.1", } CVAT_EXPORT_FORMAT_TO_DM_MAPPING = { @@ -581,12 +581,12 @@ def postprocess_annotations( """ Processes annotations and updates the files list inplace """ - processor_classes: Dict[TaskType, Type[_TaskProcessor]] = { - TaskType.image_label_binary: _LabelsTaskProcessor, - TaskType.image_boxes: _BoxesTaskProcessor, - TaskType.image_points: _PointsTaskProcessor, - TaskType.image_boxes_from_points: _BoxesFromPointsTaskProcessor, - TaskType.image_skeletons_from_boxes: _SkeletonsFromBoxesTaskProcessor, + processor_classes: Dict[TaskTypes, Type[_TaskProcessor]] = { + TaskTypes.image_label_binary: _LabelsTaskProcessor, + TaskTypes.image_boxes: _BoxesTaskProcessor, + TaskTypes.image_points: _PointsTaskProcessor, + TaskTypes.image_boxes_from_points: _BoxesFromPointsTaskProcessor, + TaskTypes.image_skeletons_from_boxes: _SkeletonsFromBoxesTaskProcessor, } task_type = manifest.annotation.type diff --git a/packages/examples/cvat/exchange-oracle/src/models/cvat.py b/packages/examples/cvat/exchange-oracle/src/models/cvat.py index e8a5d1672b..d6193f457a 100644 --- a/packages/examples/cvat/exchange-oracle/src/models/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/models/cvat.py @@ -8,12 +8,12 @@ from sqlalchemy.sql import func from src.core.types import ( - AssignmentStatus, + AssignmentStatuses, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.db import Base from src.utils.time import utcnow @@ -25,7 +25,7 @@ class Project(Base): cvat_id = Column(Integer, unique=True, index=True, nullable=False) cvat_cloudstorage_id = Column(Integer, index=True, nullable=False) status = Column(String, Enum(ProjectStatuses), nullable=False) - job_type = Column(String, Enum(TaskType), nullable=False) + job_type = Column(String, Enum(TaskTypes), nullable=False) escrow_address = Column( String(42), unique=False, nullable=False ) # TODO: extract into a separate model @@ -64,7 +64,7 @@ class Task(Base): ForeignKey("projects.cvat_id", ondelete="CASCADE"), nullable=False, ) - status = Column(String, Enum(TaskStatus), nullable=False) + status = Column(String, Enum(TaskStatuses), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) @@ -159,8 +159,8 @@ class Assignment(Base): cvat_job_id = Column(Integer, ForeignKey("jobs.cvat_id", ondelete="CASCADE"), nullable=False) status = Column( String, - Enum(AssignmentStatus), - server_default=AssignmentStatus.created.value, + Enum(AssignmentStatuses), + server_default=AssignmentStatuses.created.value, nullable=False, ) @@ -172,7 +172,7 @@ def is_finished(self) -> bool: return ( self.completed_at or utcnow() > self.expires_at - or self.status != AssignmentStatus.created + or self.status != AssignmentStatuses.created ) def __repr__(self): diff --git a/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py b/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py index 96398c1bbf..db156e7f8c 100644 --- a/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/schemas/exchange.py @@ -3,7 +3,7 @@ from pydantic import AnyUrl, BaseModel, Field -from src.core.types import PlatformType, ProjectStatuses, TaskType +from src.core.types import PlatformTypes, ProjectStatuses, TaskTypes class AssignmentResponse(BaseModel): @@ -17,11 +17,11 @@ class TaskResponse(BaseModel): escrow_address: str title: str description: str - platform: PlatformType + platform: PlatformTypes job_bounty: str job_size: int job_time_limit: int - job_type: TaskType + job_type: TaskTypes assignment: Optional[AssignmentResponse] = None status: ProjectStatuses diff --git a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py index ac3e7afcf4..79cedafdcb 100644 --- a/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/schemas/webhook.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, validator from src.chain.web3 import validate_address -from src.core.types import JobLauncherEventType, Networks +from src.core.types import JobLauncherEventTypes, Networks class OracleWebhook(BaseModel): @@ -24,7 +24,7 @@ class Config: "example": { "escrow_address": "0x199c44cfa6a84554ac01f3e3b01d7cfce38a75eb", "chain_id": 80001, - "event_type": JobLauncherEventType.escrow_created.value, + "event_type": JobLauncherEventTypes.escrow_created.value, "event_data": {}, } } diff --git a/packages/examples/cvat/exchange-oracle/src/services/cvat.py b/packages/examples/cvat/exchange-oracle/src/services/cvat.py index 60d2e08664..c467244b69 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/cvat.py +++ b/packages/examples/cvat/exchange-oracle/src/services/cvat.py @@ -6,7 +6,7 @@ from sqlalchemy import delete, insert, update from sqlalchemy.orm import Session -from src.core.types import AssignmentStatus, JobStatuses, ProjectStatuses, TaskStatus, TaskType +from src.core.types import AssignmentStatuses, JobStatuses, ProjectStatuses, TaskStatuses, TaskTypes from src.db.utils import ForUpdateParams from src.db.utils import maybe_for_update as _maybe_for_update from src.models.cvat import Assignment, DataUpload, Image, Job, Project, Task, User @@ -125,7 +125,7 @@ def get_projects_by_status( session: Session, status: ProjectStatuses, *, - included_types: Optional[Sequence[TaskType]] = None, + included_types: Optional[Sequence[TaskTypes]] = None, limit: int = 5, for_update: Union[bool, ForUpdateParams] = False, ) -> List[Project]: @@ -150,7 +150,7 @@ def get_available_projects( (Project.status == ProjectStatuses.annotation.value) & Project.jobs.any( (Job.status == JobStatuses.new) - & ~Job.assignments.any(Assignment.status == AssignmentStatus.created.value) + & ~Job.assignments.any(Assignment.status == AssignmentStatuses.created.value) ) ) .distinct() @@ -174,9 +174,9 @@ def get_projects_by_assignee( (Assignment.user_wallet_address == wallet_address) & Assignment.status.in_( [ - AssignmentStatus.created, - AssignmentStatus.completed, - AssignmentStatus.canceled, + AssignmentStatuses.created, + AssignmentStatuses.completed, + AssignmentStatuses.canceled, ] ) ) @@ -208,7 +208,7 @@ def is_project_completed(session: Session, project_id: str) -> bool: # Task -def create_task(session: Session, cvat_id: int, cvat_project_id: int, status: TaskStatus) -> str: +def create_task(session: Session, cvat_id: int, cvat_project_id: int, status: TaskStatuses) -> str: """ Create a task from CVAT. """ @@ -244,7 +244,7 @@ def get_tasks_by_cvat_id( def get_tasks_by_status( - session: Session, status: TaskStatus, *, for_update: Union[bool, ForUpdateParams] = False + session: Session, status: TaskStatuses, *, for_update: Union[bool, ForUpdateParams] = False ) -> List[Task]: return ( _maybe_for_update(session.query(Task), enable=for_update) @@ -253,7 +253,7 @@ def get_tasks_by_status( ) -def update_task_status(session: Session, task_id: int, status: TaskStatus) -> None: +def update_task_status(session: Session, task_id: int, status: TaskStatuses) -> None: upd = update(Task).where(Task.id == task_id).values(status=status.value) session.execute(upd) @@ -457,7 +457,7 @@ def get_unprocessed_expired_assignments( return ( _maybe_for_update(session.query(Assignment), enable=for_update) .where( - (Assignment.status == AssignmentStatus.created.value) + (Assignment.status == AssignmentStatuses.created.value) & (Assignment.completed_at == None) & (Assignment.expires_at <= utcnow()) ) @@ -472,7 +472,7 @@ def get_active_assignments( return ( _maybe_for_update(session.query(Assignment), enable=for_update) .where( - (Assignment.status == AssignmentStatus.created.value) + (Assignment.status == AssignmentStatuses.created.value) & (Assignment.completed_at == None) & (Assignment.expires_at <= utcnow()) ) @@ -485,7 +485,7 @@ def update_assignment( session: Session, id: str, *, - status: AssignmentStatus, + status: AssignmentStatuses, completed_at: Optional[datetime] = None, ): statement = ( @@ -497,11 +497,11 @@ def update_assignment( def cancel_assignment(session: Session, assignment_id: str): - update_assignment(session, assignment_id, status=AssignmentStatus.canceled) + update_assignment(session, assignment_id, status=AssignmentStatuses.canceled) def expire_assignment(session: Session, assignment_id: str): - update_assignment(session, assignment_id, status=AssignmentStatus.expired) + update_assignment(session, assignment_id, status=AssignmentStatuses.expired) def complete_assignment(session: Session, assignment_id: str, completed_at: datetime): @@ -509,7 +509,7 @@ def complete_assignment(session: Session, assignment_id: str, completed_at: date session, assignment_id, completed_at=completed_at, - status=AssignmentStatus.completed, + status=AssignmentStatuses.completed, ) diff --git a/packages/examples/cvat/exchange-oracle/src/services/exchange.py b/packages/examples/cvat/exchange-oracle/src/services/exchange.py index a3091f1f92..b2989bf0ab 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/services/exchange.py @@ -5,7 +5,7 @@ import src.models.cvat as models import src.services.cvat as cvat_service from src.chain.escrow import get_escrow_manifest -from src.core.types import AssignmentStatus, JobStatuses, PlatformType, ProjectStatuses +from src.core.types import AssignmentStatuses, JobStatuses, PlatformTypes, ProjectStatuses from src.db import SessionLocal from src.schemas import exchange as service_api from src.utils.assignments import compose_assignment_url, parse_manifest @@ -46,7 +46,7 @@ def serialize_task( job_time_limit=manifest.annotation.max_time, job_size=manifest.annotation.job_size + manifest.validation.val_size, job_type=project.job_type, - platform=PlatformType.CVAT, + platform=PlatformTypes.CVAT, assignment=serialized_assignment, status=project.status, ) @@ -80,7 +80,7 @@ def get_tasks_by_assignee( wallet_address=wallet_address, cvat_projects=[p.cvat_id for p in cvat_projects], ) - if assignment.status == AssignmentStatus.created + if assignment.status == AssignmentStatuses.created } for project in cvat_projects: diff --git a/packages/examples/cvat/exchange-oracle/src/services/webhook.py b/packages/examples/cvat/exchange-oracle/src/services/webhook.py index 57f025f6a4..f0eaead660 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/webhook.py +++ b/packages/examples/cvat/exchange-oracle/src/services/webhook.py @@ -17,14 +17,14 @@ from src.utils.time import utcnow -class OracleWebhookDirectionTag(str, Enum, metaclass=BetterEnumMeta): +class OracleWebhookDirectionTags(str, Enum, metaclass=BetterEnumMeta): incoming = "incoming" outgoing = "outgoing" @define class OracleWebhookQueue: - direction: OracleWebhookDirectionTag + direction: OracleWebhookDirectionTags default_sender: Optional[OracleWebhookTypes] = None def create_webhook( @@ -47,7 +47,7 @@ def create_webhook( ), f"'event' and 'event_type' cannot be used together. Please use only one of the fields" if event_type: - if self.direction == OracleWebhookDirectionTag.incoming: + if self.direction == OracleWebhookDirectionTags.incoming: sender = type else: assert self.default_sender @@ -57,9 +57,9 @@ def create_webhook( event_type = event.get_type() event_data = event.dict() - if self.direction == OracleWebhookDirectionTag.incoming and not signature: + if self.direction == OracleWebhookDirectionTags.incoming and not signature: raise ValueError("Webhook signature must be specified for incoming events") - elif self.direction == OracleWebhookDirectionTag.outgoing and signature: + elif self.direction == OracleWebhookDirectionTags.outgoing and signature: raise ValueError("Webhook signature must not be specified for outgoing events") if signature: @@ -144,8 +144,8 @@ def handle_webhook_fail(self, session: Session, webhook_id: str) -> None: session.execute(upd) -inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTag.incoming) +inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTags.incoming) outbox = OracleWebhookQueue( - direction=OracleWebhookDirectionTag.outgoing, + direction=OracleWebhookDirectionTags.outgoing, default_sender=OracleWebhookTypes.exchange_oracle, ) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index 37b5c39242..3037238da8 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -1,7 +1,7 @@ from urllib.parse import urljoin from src.core.config import Config -from src.core.manifest import TaskManifest, TaskType +from src.core.manifest import TaskManifest, TaskTypes from src.core.manifest import parse_manifest as _parse_manifest from src.models.cvat import Project @@ -12,7 +12,10 @@ def parse_manifest(manifest: dict) -> TaskManifest: def compose_assignment_url(task_id: int, job_id: int, *, project: Project) -> str: query_params = "" - if project.job_type in [TaskType.image_skeletons_from_boxes, TaskType.image_boxes_from_points]: + if project.job_type in [ + TaskTypes.image_skeletons_from_boxes, + TaskTypes.image_boxes_from_points, + ]: query_params = "?defaultWorkspace=single_shape" return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}{query_params}") diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py index 59a106419f..2feb6b8300 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_cvat_webhook_api.py @@ -3,7 +3,7 @@ from fastapi.testclient import TestClient -from src.core.types import AssignmentStatus, JobStatuses +from src.core.types import AssignmentStatuses, JobStatuses from tests.utils.setup_cvat import ( add_asignment_to_db, @@ -109,7 +109,7 @@ def test_incoming_webhook_200_update_expired_assignmets(client: TestClient) -> N (job, asignees) = get_cvat_job_from_db(1) assert job.status == JobStatuses.new.value - assert asignees[0].status == AssignmentStatus.expired.value + assert asignees[0].status == AssignmentStatuses.expired.value def test_incoming_webhook_200_update(client: TestClient) -> None: @@ -152,7 +152,7 @@ def test_incoming_webhook_200_update(client: TestClient) -> None: (job, asignees) = get_cvat_job_from_db(1) assert job.status == JobStatuses.completed.value - assert asignees[0].status == AssignmentStatus.completed.value + assert asignees[0].status == AssignmentStatuses.completed.value data = { diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py index 4d36dcf465..809e523c9e 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_exchange_api.py @@ -5,7 +5,7 @@ from fastapi.testclient import TestClient -from src.core.types import AssignmentStatus +from src.core.types import AssignmentStatuses from src.db import SessionLocal from src.models.cvat import Assignment, User @@ -317,7 +317,7 @@ def test_create_assignment_200(client: TestClient) -> None: assert db_assignment.cvat_job_id == cvat_job_1.cvat_id assert db_assignment.user_wallet_address == user_address - assert db_assignment.status == AssignmentStatus.created + assert db_assignment.status == AssignmentStatuses.created assert response.json()["assignment"] session.close() diff --git a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py index 64164fcf11..4d62b8fa2c 100644 --- a/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py +++ b/packages/examples/cvat/exchange-oracle/tests/api/test_webhook_api.py @@ -4,7 +4,7 @@ from sqlalchemy.sql import select from web3 import HTTPProvider, Web3 -from src.core.types import JobLauncherEventType, Networks +from src.core.types import JobLauncherEventTypes, Networks from src.db import SessionLocal from src.models.webhook import Webhook @@ -45,7 +45,7 @@ def test_incoming_webhook_200(client: TestClient) -> None: ) assert webhook.escrow_address == escrow_address assert webhook.chain_id == 80001 - assert webhook.event_type == JobLauncherEventType.escrow_created.value + assert webhook.event_type == JobLauncherEventTypes.escrow_created.value assert webhook.event_data == {} assert webhook.direction == "incoming" assert webhook.signature == WEBHOOK_MESSAGE_SIGNED diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py index 5d339072b1..446482bfae 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_assignments.py @@ -7,12 +7,12 @@ from sqlalchemy.sql import select from src.core.types import ( - AssignmentStatus, + AssignmentStatuses, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.crons.state_trackers import track_assignments from src.db import SessionLocal @@ -67,8 +67,8 @@ def test_track_expired_assignments(self): db_assignments = sorted( self.session.query(Assignment).all(), key=lambda assignment: assignment.user.cvat_id ) - self.assertEqual(db_assignments[0].status, AssignmentStatus.created.value) - self.assertEqual(db_assignments[1].status, AssignmentStatus.created.value) + self.assertEqual(db_assignments[0].status, AssignmentStatuses.created.value) + self.assertEqual(db_assignments[1].status, AssignmentStatuses.created.value) with patch("src.crons.state_trackers.cvat_api.update_job_assignee") as mock_cvat_api: track_assignments() @@ -79,8 +79,8 @@ def test_track_expired_assignments(self): db_assignments = sorted( self.session.query(Assignment).all(), key=lambda assignment: assignment.user.cvat_id ) - self.assertEqual(db_assignments[0].status, AssignmentStatus.created.value) - self.assertEqual(db_assignments[1].status, AssignmentStatus.expired.value) + self.assertEqual(db_assignments[0].status, AssignmentStatuses.created.value) + self.assertEqual(db_assignments[1].status, AssignmentStatuses.expired.value) # TODO: # Fix src/crons/state_trackers.py diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py index f6ba463e14..0259a18976 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_escrows.py @@ -12,12 +12,12 @@ import datumaro as dm from src.core.types import ( - ExchangeOracleEventType, + ExchangeOracleEventTypes, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.crons.state_trackers import track_completed_escrows from src.db import SessionLocal @@ -41,7 +41,7 @@ def test_retrieve_annotations(self): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -61,7 +61,7 @@ def test_retrieve_annotations(self): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -139,7 +139,7 @@ def test_retrieve_annotations(self): .first() ) self.assertIsNotNone(webhook) - self.assertEqual(webhook.event_type, ExchangeOracleEventType.task_finished) + self.assertEqual(webhook.event_type, ExchangeOracleEventTypes.task_finished) db_project = self.session.query(Project).filter_by(id=project_id).first() self.assertEqual(db_project.status, ProjectStatuses.validation) @@ -153,7 +153,7 @@ def test_retrieve_annotations_unfinished_jobs(self): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -165,7 +165,7 @@ def test_retrieve_annotations_unfinished_jobs(self): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -196,7 +196,7 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -208,7 +208,7 @@ def test_retrieve_annotations_error_getting_annotations(self, mock_annotations): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -286,7 +286,7 @@ def test_retrieve_annotations_error_uploading_files(self): cvat_id=cvat_project_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -298,7 +298,7 @@ def test_retrieve_annotations_error_uploading_files(self): id=str(uuid.uuid4()), cvat_id=cvat_task_id, cvat_project_id=cvat_project_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py index 698b533a78..489215e0db 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_projects.py @@ -3,7 +3,7 @@ from sqlalchemy.sql import select -from src.core.types import Networks, ProjectStatuses, TaskStatus, TaskType +from src.core.types import Networks, ProjectStatuses, TaskStatuses, TaskTypes from src.crons.state_trackers import track_completed_projects from src.db import SessionLocal from src.models.cvat import Project, Task @@ -23,7 +23,7 @@ def test_track_completed_projects(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -33,7 +33,7 @@ def test_track_completed_projects(self): id=str(uuid.uuid4()), cvat_id=1, cvat_project_id=1, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(project) @@ -55,7 +55,7 @@ def test_track_completed_projects_with_unfinished_task(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -65,13 +65,13 @@ def test_track_completed_projects_with_unfinished_task(self): id=str(uuid.uuid4()), cvat_id=1, cvat_project_id=1, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) task_2 = Task( id=str(uuid.uuid4()), cvat_id=2, cvat_project_id=1, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) self.session.add(project) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py index c665c8a260..4619c5b7cf 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_completed_tasks.py @@ -3,7 +3,7 @@ from sqlalchemy.sql import select -from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatus, TaskType +from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatuses, TaskTypes from src.crons.state_trackers import track_completed_tasks from src.db import SessionLocal from src.models.cvat import Job, Project, Task @@ -22,7 +22,7 @@ def test_track_completed_tasks(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -33,7 +33,7 @@ def test_track_completed_tasks(self): id=task_id, cvat_id=1, cvat_project_id=1, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) job = Job( @@ -55,7 +55,7 @@ def test_track_completed_tasks(self): self.session.execute(select(Task).where(Task.id == task_id)).scalars().first() ) - self.assertEqual(updated_task.status, TaskStatus.completed.value) + self.assertEqual(updated_task.status, TaskStatuses.completed.value) def test_track_completed_tasks_with_unfinished_job(self): project = Project( @@ -63,7 +63,7 @@ def test_track_completed_tasks_with_unfinished_job(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address="0x86e83d346041E8806e352681f3F14549C0d2BC67", chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -74,7 +74,7 @@ def test_track_completed_tasks_with_unfinished_job(self): id=task_id, cvat_id=1, cvat_project_id=1, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) job_1 = Job( @@ -104,4 +104,4 @@ def test_track_completed_tasks_with_unfinished_job(self): self.session.execute(select(Task).where(Task.id == task_id)).scalars().first() ) - self.assertEqual(updated_task.status, TaskStatus.annotation.value) + self.assertEqual(updated_task.status, TaskStatuses.annotation.value) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py index 3c98f4b1fb..5d24c68717 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py @@ -3,7 +3,7 @@ from unittest.mock import Mock, patch import src.cvat.api_calls as cvat_api -from src.core.types import ExchangeOracleEventType, JobStatuses +from src.core.types import ExchangeOracleEventTypes, JobStatuses from src.crons.state_trackers import track_task_creation from src.db import SessionLocal from src.models.cvat import DataUpload, Job diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index d5ac4e5b9f..7840da3b9a 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -7,15 +7,15 @@ from sqlalchemy.sql import select from src.core.types import ( - ExchangeOracleEventType, - JobLauncherEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, JobStatuses, Networks, OracleWebhookStatuses, OracleWebhookTypes, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.crons.process_job_launcher_webhooks import ( process_incoming_job_launcher_webhooks, @@ -24,7 +24,7 @@ from src.db import SessionLocal from src.models.cvat import Job, Project, Task from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags from tests.utils.constants import DEFAULT_MANIFEST_URL, JOB_LAUNCHER_ADDRESS @@ -48,8 +48,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -109,8 +109,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_invalid_escr chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -140,8 +140,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_exceed_max_r chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, attempts=5, ) @@ -185,8 +185,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_ chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -237,7 +237,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -252,8 +252,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_canceled.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_canceled.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -289,7 +289,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -304,8 +304,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_sta chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_canceled.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_canceled.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -340,7 +340,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -355,8 +355,8 @@ def test_process_incoming_job_launcher_webhooks_escrow_canceled_type_invalid_bal chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_canceled.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_canceled.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -394,8 +394,8 @@ def test_process_outgoing_job_launcher_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=ExchangeOracleEventType.task_finished.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=ExchangeOracleEventTypes.task_finished.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) @@ -436,8 +436,8 @@ def test_process_outgoing_job_launcher_webhooks_invalid_type(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py index e7f534cc90..f3dbdc35f2 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_recording_oracle_webhooks.py @@ -6,15 +6,15 @@ from sqlalchemy.sql import select from src.core.types import ( - ExchangeOracleEventType, + ExchangeOracleEventTypes, JobStatuses, Networks, OracleWebhookStatuses, OracleWebhookTypes, ProjectStatuses, - RecordingOracleEventType, - TaskStatus, - TaskType, + RecordingOracleEventTypes, + TaskStatuses, + TaskTypes, ) from src.crons.process_recording_oracle_webhooks import ( process_incoming_recording_oracle_webhooks, @@ -23,7 +23,7 @@ from src.db import SessionLocal from src.models.cvat import Job, Project, Task from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags from tests.utils.constants import DEFAULT_MANIFEST_URL, RECORDING_ORACLE_ADDRESS @@ -45,7 +45,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.validation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -60,8 +60,8 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -88,7 +88,7 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ cvat_id=1, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -103,8 +103,8 @@ def test_process_incoming_recording_oracle_webhooks_task_completed_type_invalid_ chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=OracleWebhookDirectionTag.incoming, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -132,7 +132,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self cvat_id=cvat_id, cvat_cloudstorage_id=1, status=ProjectStatuses.validation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -143,7 +143,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self id=task_id, cvat_id=cvat_id, cvat_project_id=cvat_project.cvat_id, - status=TaskStatus.completed.value, + status=TaskStatuses.completed.value, ) self.session.add(cvat_task) @@ -165,9 +165,9 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_rejected.value, + event_type=RecordingOracleEventTypes.task_rejected.value, event_data={"rejected_job_ids": [cvat_id]}, - direction=OracleWebhookDirectionTag.incoming, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -187,7 +187,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type(self db_task = self.session.query(Task).filter_by(id=task_id).first() - self.assertEqual(db_task.status, TaskStatus.annotation.value) + self.assertEqual(db_task.status, TaskStatuses.annotation.value) db_job = self.session.query(Job).filter_by(id=job_id).first() @@ -204,7 +204,7 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva cvat_id=cvat_id, cvat_cloudstorage_id=1, status=ProjectStatuses.completed.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -219,9 +219,9 @@ def test_process_incoming_recording_oracle_webhooks_task_task_rejected_type_inva chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_rejected.value, + event_type=RecordingOracleEventTypes.task_rejected.value, event_data={"rejected_job_ids": [cvat_id]}, - direction=OracleWebhookDirectionTag.incoming, + direction=OracleWebhookDirectionTags.incoming, ) self.session.add(webhook) @@ -251,8 +251,8 @@ def test_process_outgoing_recording_oracle_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=ExchangeOracleEventType.task_finished.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=ExchangeOracleEventTypes.task_finished.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) @@ -293,8 +293,8 @@ def test_process_outgoing_recording_oracle_webhooks_invalid_type(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=OracleWebhookDirectionTag.outgoing, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py index f6c1855087..b511849451 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_cvat.py @@ -7,12 +7,12 @@ import src.services.cvat as cvat_service from src.core.types import ( - AssignmentStatus, + AssignmentStatuses, JobStatuses, Networks, ProjectStatuses, - TaskStatus, - TaskType, + TaskStatuses, + TaskTypes, ) from src.db import SessionLocal from src.models.cvat import Assignment, DataUpload, Image, Job, Project, Task, User @@ -33,7 +33,7 @@ def tearDown(self): def test_create_project(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value cvat_cloudstorage_id = 1 @@ -61,7 +61,7 @@ def test_create_project(self): def test_create_duplicated_project(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value cvat_cloudstorage_id = 1 @@ -91,7 +91,7 @@ def test_create_duplicated_project(self): def test_create_project_none_cvat_id(self): cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -109,7 +109,7 @@ def test_create_project_none_cvat_id(self): def test_create_project_none_cvat_cloudstorage_id(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -146,7 +146,7 @@ def test_create_project_none_job_type(self): def test_create_project_none_escrow_address(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" cvat_service.create_project( @@ -163,7 +163,7 @@ def test_create_project_none_escrow_address(self): def test_create_project_none_chain_id(self): cvat_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" cvat_cloudstorage_id = 1 bucket_url = "https://test.storage.googleapis.com/" @@ -183,7 +183,7 @@ def test_create_project_none_chain_id(self): def test_create_project_none_bucket_url(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value cvat_service.create_project( @@ -201,7 +201,7 @@ def test_create_project_none_bucket_url(self): def test_get_project_by_id(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -250,7 +250,7 @@ def test_get_project_by_id(self): def test_get_project_by_escrow_address(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -282,7 +282,7 @@ def test_get_project_by_escrow_address(self): def test_get_projects_by_status(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -298,7 +298,7 @@ def test_get_projects_by_status(self): cvat_id = 2 cvat_cloudstorage_id = 2 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC68" bucket_url = "https://test2.storage.googleapis.com/" cvat_service.create_project( @@ -313,7 +313,7 @@ def test_get_projects_by_status(self): cvat_id = 3 cvat_cloudstorage_id = 3 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC69" bucket_url = "https://test3.storage.googleapis.com/" cvat_service.create_project( @@ -424,7 +424,7 @@ def test_get_projects_by_assignee(self): def test_update_project_status(self): cvat_id = 1 cvat_cloudstorage_id = 1 - job_type = TaskType.image_label_binary.value + job_type = TaskTypes.image_label_binary.value escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" chain_id = Networks.localhost.value bucket_url = "https://test.storage.googleapis.com/" @@ -523,7 +523,7 @@ def test_create_task(self): self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", cvat_id ) - status = TaskStatus.annotation + status = TaskStatuses.annotation task_id = cvat_service.create_task(self.session, cvat_id, cvat_project.cvat_id, status) @@ -541,7 +541,7 @@ def test_create_task_duplicated_cvat_id(self): self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", cvat_id ) - status = TaskStatus.annotation + status = TaskStatuses.annotation cvat_service.create_task(self.session, cvat_id, cvat_project.cvat_id, status) self.session.commit() @@ -551,17 +551,17 @@ def test_create_task_duplicated_cvat_id(self): self.session.commit() def test_create_tas_without_project(self): - cvat_service.create_task(self.session, 123, 123, TaskStatus.annotation) + cvat_service.create_task(self.session, 123, 123, TaskStatuses.annotation) with self.assertRaises(IntegrityError): self.session.commit() def test_create_task_none_cvat_id(self): - cvat_service.create_task(self.session, None, 123, TaskStatus.annotation) + cvat_service.create_task(self.session, None, 123, TaskStatuses.annotation) with self.assertRaises(IntegrityError): self.session.commit() def test_create_task_none_cvat_project_id(self): - cvat_service.create_task(self.session, 123, None, TaskStatus.annotation) + cvat_service.create_task(self.session, 123, None, TaskStatuses.annotation) with self.assertRaises(IntegrityError): self.session.commit() @@ -569,7 +569,7 @@ def test_get_task_by_id(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) task_id = cvat_service.create_task( - self.session, 1, cvat_project.cvat_id, TaskStatus.annotation + self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation ) task = cvat_service.get_task_by_id(self.session, task_id) @@ -577,7 +577,7 @@ def test_get_task_by_id(self): self.assertEqual(task.id, task_id) self.assertEqual(task.cvat_id, cvat_project.cvat_id) self.assertEqual(task.cvat_project_id, cvat_project.cvat_id) - self.assertEqual(task.status, TaskStatus.annotation.value) + self.assertEqual(task.status, TaskStatuses.annotation.value) task = cvat_service.get_task_by_id(self.session, "dummy_id") @@ -586,9 +586,9 @@ def test_get_task_by_id(self): def test_get_tasks_by_cvat_id(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) - cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatus.annotation) + cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatuses.annotation) tasks = cvat_service.get_tasks_by_cvat_id(self.session, [1, 2]) @@ -605,15 +605,15 @@ def test_get_tasks_by_cvat_id(self): def test_get_tasks_by_status(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) - cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatus.completed) + cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatuses.completed) - tasks = cvat_service.get_tasks_by_status(self.session, TaskStatus.annotation) + tasks = cvat_service.get_tasks_by_status(self.session, TaskStatuses.annotation) self.assertEqual(len(tasks), 2) - tasks = cvat_service.get_tasks_by_status(self.session, TaskStatus.completed) + tasks = cvat_service.get_tasks_by_status(self.session, TaskStatuses.completed) self.assertEqual(len(tasks), 1) @@ -621,10 +621,10 @@ def test_update_task_status(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) task_id = cvat_service.create_task( - self.session, 1, cvat_project.cvat_id, TaskStatus.annotation + self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation ) - cvat_service.update_task_status(self.session, task_id, TaskStatus.completed) + cvat_service.update_task_status(self.session, task_id, TaskStatuses.completed) task = cvat_service.get_task_by_id(self.session, task_id) @@ -632,20 +632,20 @@ def test_update_task_status(self): self.assertEqual(task.id, task_id) self.assertEqual(task.cvat_id, cvat_project.cvat_id) self.assertEqual(task.cvat_project_id, cvat_project.cvat_id) - self.assertEqual(task.status, TaskStatus.completed.value) + self.assertEqual(task.status, TaskStatuses.completed.value) def test_get_tasks_by_cvat_project_id(self): cvat_project = create_project(self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC67", 1) - cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatus.annotation) - cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatus.completed) + cvat_service.create_task(self.session, 1, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 2, cvat_project.cvat_id, TaskStatuses.annotation) + cvat_service.create_task(self.session, 3, cvat_project.cvat_id, TaskStatuses.completed) cvat_project_2 = create_project( self.session, "0x86e83d346041E8806e352681f3F14549C0d2BC68", 2 ) - cvat_service.create_task(self.session, 4, cvat_project_2.cvat_id, TaskStatus.annotation) + cvat_service.create_task(self.session, 4, cvat_project_2.cvat_id, TaskStatuses.annotation) tasks = cvat_service.get_tasks_by_cvat_project_id(self.session, cvat_project.cvat_id) @@ -1200,7 +1200,7 @@ def test_create_assignment(self): self.assertIsNotNone(assignment) self.assertEqual(assignment.user_wallet_address, wallet_address) self.assertEqual(assignment.cvat_job_id, cvat_job.cvat_id) - self.assertEqual(assignment.status, AssignmentStatus.created.value) + self.assertEqual(assignment.status, AssignmentStatuses.created.value) def test_create_assignment_invalid_address(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1386,13 +1386,13 @@ def test_update_assignment(self): self.session.commit() cvat_service.update_assignment( - self.session, assignment.id, status=AssignmentStatus.completed + self.session, assignment.id, status=AssignmentStatuses.completed ) db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.completed) + self.assertEqual(db_assignment.status, AssignmentStatuses.completed) def test_cancel_assignment(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1420,7 +1420,7 @@ def test_cancel_assignment(self): db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.canceled) + self.assertEqual(db_assignment.status, AssignmentStatuses.canceled) def test_expire_assignment(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1448,7 +1448,7 @@ def test_expire_assignment(self): db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.expired) + self.assertEqual(db_assignment.status, AssignmentStatuses.expired) def test_complete_assignment(self): (_, _, cvat_job) = create_project_task_and_job( @@ -1476,7 +1476,7 @@ def test_complete_assignment(self): db_assignment = self.session.query(Assignment).filter_by(id=assignment.id).first() self.assertEqual(db_assignment.id, assignment.id) - self.assertEqual(db_assignment.status, AssignmentStatus.completed) + self.assertEqual(db_assignment.status, AssignmentStatuses.completed) self.assertEqual(db_assignment.completed_at, completed_date) def test_test_add_project_images(self): diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py index 60859f2927..b339ceef34 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_exchange.py @@ -8,7 +8,7 @@ from pydantic import ValidationError import src.services.cvat as cvat_service -from src.core.types import AssignmentStatus, PlatformType, ProjectStatuses +from src.core.types import AssignmentStatuses, PlatformTypes, ProjectStatuses from src.db import SessionLocal from src.models.cvat import Assignment, User from src.schemas import exchange as service_api @@ -53,7 +53,7 @@ def test_serialize_task(self): self.assertIsInstance(data.job_time_limit, int) self.assertIsInstance(data.job_size, int) self.assertEqual(data.job_type, cvat_project.job_type) - self.assertEqual(data.platform, PlatformType.CVAT) + self.assertEqual(data.platform, PlatformTypes.CVAT) self.assertEqual(data.status, cvat_project.status) self.assertIsNone(data.assignment) self.assertIsInstance(data, service_api.TaskResponse) @@ -100,7 +100,7 @@ def test_serialize_task_with_assignment(self): self.assertIsInstance(data.job_time_limit, int) self.assertIsInstance(data.job_size, int) self.assertEqual(data.job_type, cvat_project.job_type) - self.assertEqual(data.platform, PlatformType.CVAT) + self.assertEqual(data.platform, PlatformTypes.CVAT) self.assertEqual(data.status, cvat_project.status) self.assertIsNotNone(data.assignment) self.assertIsInstance(data.assignment.assignment_url, str) @@ -225,7 +225,7 @@ def test_create_assignment(self): self.assertEqual(assignment.cvat_job_id, cvat_job_1.cvat_id) self.assertEqual(assignment.user_wallet_address, user_address) - self.assertEqual(assignment.status, AssignmentStatus.created) + self.assertEqual(assignment.status, AssignmentStatuses.created) def test_create_assignment_invalid_user_address(self): cvat_project_1, _, _ = create_project_task_and_job( diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py index 2569553479..5bd398d528 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/services/test_webhook.py @@ -6,12 +6,12 @@ import src.services.webhook as webhook_service from src.core.oracle_events import ExchangeOracleEvent_TaskFinished from src.core.types import ( - ExchangeOracleEventType, - JobLauncherEventType, + ExchangeOracleEventTypes, + JobLauncherEventTypes, Networks, OracleWebhookStatuses, OracleWebhookTypes, - RecordingOracleEventType, + RecordingOracleEventTypes, ) from src.db import SessionLocal from src.models.webhook import Webhook @@ -35,7 +35,7 @@ def test_create_incoming_webhook(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook = self.session.query(Webhook).filter_by(id=webhook_id).first() @@ -45,7 +45,7 @@ def test_create_incoming_webhook(self): self.assertEqual(webhook.signature, signature) self.assertEqual(webhook.attempts, 0) self.assertEqual(webhook.type, OracleWebhookTypes.job_launcher.value) - self.assertEqual(webhook.event_type, JobLauncherEventType.escrow_created.value) + self.assertEqual(webhook.event_type, JobLauncherEventTypes.escrow_created.value) self.assertEqual(webhook.event_data, None) self.assertEqual(webhook.status, OracleWebhookStatuses.pending.value) @@ -58,7 +58,7 @@ def test_create_incoming_webhook_none_escrow_address(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) with self.assertRaises(IntegrityError): self.session.commit() @@ -72,7 +72,7 @@ def test_create_incoming_webhook_none_chain_id(self): chain_id=None, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) with self.assertRaises(IntegrityError): self.session.commit() @@ -103,7 +103,7 @@ def test_create_incoming_webhook_none_signature(self): escrow_address=escrow_address, chain_id=chain_id, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) self.assertEqual( str(error.exception), "Webhook signature must be specified for incoming events" @@ -127,7 +127,7 @@ def test_create_outgoing_webhook(self): self.assertEqual(webhook.chain_id, chain_id) self.assertEqual(webhook.attempts, 0) self.assertEqual(webhook.type, OracleWebhookTypes.exchange_oracle.value) - self.assertEqual(webhook.event_type, ExchangeOracleEventType.task_finished.value) + self.assertEqual(webhook.event_type, ExchangeOracleEventTypes.task_finished.value) self.assertEqual(webhook.event_data, {}) self.assertEqual(webhook.status, OracleWebhookStatuses.pending.value) @@ -198,8 +198,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook2_id = str(uuid.uuid4()) webhook2 = Webhook( @@ -209,8 +209,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook3_id = str(uuid.uuid4()) webhook3 = Webhook( @@ -220,8 +220,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.completed.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook4_id = str(uuid.uuid4()) @@ -232,8 +232,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.recording_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed.value, - direction=webhook_service.OracleWebhookDirectionTag.incoming, + event_type=RecordingOracleEventTypes.task_completed.value, + direction=webhook_service.OracleWebhookDirectionTags.incoming, ) webhook5_id = str(uuid.uuid4()) webhook5 = Webhook( @@ -242,8 +242,8 @@ def test_get_pending_webhooks(self): chain_id=chain_id, type=OracleWebhookTypes.job_launcher.value, status=OracleWebhookStatuses.pending.value, - event_type=JobLauncherEventType.escrow_created.value, - direction=webhook_service.OracleWebhookDirectionTag.outgoing, + event_type=JobLauncherEventTypes.escrow_created.value, + direction=webhook_service.OracleWebhookDirectionTags.outgoing, ) self.session.add(webhook1) @@ -283,7 +283,7 @@ def test_update_webhook_status(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook_service.inbox.update_webhook_status( @@ -310,7 +310,7 @@ def test_handle_webhook_success(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook_service.inbox.handle_webhook_success(self.session, webhook_id) @@ -335,7 +335,7 @@ def test_handle_webhook_fail(self): chain_id=chain_id, signature=signature, type=OracleWebhookTypes.job_launcher, - event_type=JobLauncherEventType.escrow_created.value, + event_type=JobLauncherEventTypes.escrow_created.value, ) webhook_service.inbox.handle_webhook_fail(self.session, webhook_id) diff --git a/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py b/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py index e315357dc5..9133b92031 100644 --- a/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py +++ b/packages/examples/cvat/exchange-oracle/tests/utils/db_helper.py @@ -1,6 +1,6 @@ import uuid -from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatus, TaskType +from src.core.types import JobStatuses, Networks, ProjectStatuses, TaskStatuses, TaskTypes from src.db import SessionLocal from src.models.cvat import Job, Project, Task @@ -11,7 +11,7 @@ def create_project(session: SessionLocal, escrow_address: str, cvat_id: int) -> cvat_id=cvat_id, cvat_cloudstorage_id=1, status=ProjectStatuses.annotation.value, - job_type=TaskType.image_label_binary.value, + job_type=TaskTypes.image_label_binary.value, escrow_address=escrow_address, chain_id=Networks.localhost.value, bucket_url="https://test.storage.googleapis.com/", @@ -27,7 +27,7 @@ def create_project_and_task(session: SessionLocal, escrow_address: str, cvat_id: id=str(uuid.uuid4()), cvat_id=cvat_id, cvat_project_id=cvat_id, - status=TaskStatus.annotation.value, + status=TaskStatuses.annotation.value, ) session.add(cvat_task) diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 7c3afab308..a3dbef6499 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -5,7 +5,7 @@ from pydantic import AnyUrl, BaseModel, Field, root_validator from src.core.config import Config -from src.core.types import TaskType +from src.core.types import TaskTypes from src.utils.enums import BetterEnumMeta @@ -119,7 +119,7 @@ def validate_type(cls, values: dict) -> dict: class AnnotationInfo(BaseModel): - type: TaskType + type: TaskTypes labels: list[LabelInfo] = Field(min_items=1) "Label declarations with accepted annotation types" diff --git a/packages/examples/cvat/recording-oracle/src/core/oracle_events.py b/packages/examples/cvat/recording-oracle/src/core/oracle_events.py index 8075abd8ee..519152e2de 100644 --- a/packages/examples/cvat/recording-oracle/src/core/oracle_events.py +++ b/packages/examples/cvat/recording-oracle/src/core/oracle_events.py @@ -2,11 +2,11 @@ from pydantic import BaseModel -from src.core.types import ExchangeOracleEventType, OracleWebhookTypes, RecordingOracleEventType +from src.core.types import ExchangeOracleEventTypes, OracleWebhookTypes, RecordingOracleEventTypes EventTypeTag = Union[ - ExchangeOracleEventType, - RecordingOracleEventType, + ExchangeOracleEventTypes, + RecordingOracleEventTypes, ] @@ -43,10 +43,10 @@ class ExchangeOracleEvent_TaskFinished(OracleEvent): _event_type_map = { - RecordingOracleEventType.task_completed: RecordingOracleEvent_TaskCompleted, - RecordingOracleEventType.task_rejected: RecordingOracleEvent_TaskRejected, - ExchangeOracleEventType.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, - ExchangeOracleEventType.task_finished: ExchangeOracleEvent_TaskFinished, + RecordingOracleEventTypes.task_completed: RecordingOracleEvent_TaskCompleted, + RecordingOracleEventTypes.task_rejected: RecordingOracleEvent_TaskRejected, + ExchangeOracleEventTypes.task_creation_failed: ExchangeOracleEvent_TaskCreationFailed, + ExchangeOracleEventTypes.task_finished: ExchangeOracleEvent_TaskFinished, } @@ -72,8 +72,8 @@ def parse_event( sender: OracleWebhookTypes, event_type: str, event_data: Optional[dict] = None ) -> OracleEvent: sender_events_mapping = { - OracleWebhookTypes.recording_oracle: RecordingOracleEventType, - OracleWebhookTypes.exchange_oracle: ExchangeOracleEventType, + OracleWebhookTypes.recording_oracle: RecordingOracleEventTypes, + OracleWebhookTypes.exchange_oracle: ExchangeOracleEventTypes, } sender_events = sender_events_mapping.get(sender) diff --git a/packages/examples/cvat/recording-oracle/src/core/types.py b/packages/examples/cvat/recording-oracle/src/core/types.py index e98c0383b0..3ed9792c9c 100644 --- a/packages/examples/cvat/recording-oracle/src/core/types.py +++ b/packages/examples/cvat/recording-oracle/src/core/types.py @@ -10,7 +10,7 @@ class Networks(int, Enum): localhost = Config.localhost.chain_id -class TaskType(str, Enum, metaclass=BetterEnumMeta): +class TaskTypes(str, Enum, metaclass=BetterEnumMeta): image_label_binary = "IMAGE_LABEL_BINARY" image_points = "IMAGE_POINTS" image_boxes = "IMAGE_BOXES" @@ -30,11 +30,11 @@ class OracleWebhookStatuses(str, Enum): failed = "failed" -class ExchangeOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class ExchangeOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_creation_failed = "task_creation_failed" task_finished = "task_finished" -class RecordingOracleEventType(str, Enum, metaclass=BetterEnumMeta): +class RecordingOracleEventTypes(str, Enum, metaclass=BetterEnumMeta): task_completed = "task_completed" task_rejected = "task_rejected" diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py index aef5ebb812..86d1097ece 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_exchange_oracle_webhooks.py @@ -7,7 +7,7 @@ import src.services.webhook as oracle_db_service from src.chain.kvstore import get_exchange_oracle_url from src.core.config import Config -from src.core.types import ExchangeOracleEventType, OracleWebhookTypes +from src.core.types import ExchangeOracleEventTypes, OracleWebhookTypes from src.db import SessionLocal from src.db.utils import ForUpdateParams from src.handlers.validation import validate_results @@ -62,7 +62,7 @@ def handle_exchange_oracle_event(webhook: Webhook, *, db_session: Session, logge assert webhook.type == OracleWebhookTypes.exchange_oracle match webhook.event_type: - case ExchangeOracleEventType.task_finished: + case ExchangeOracleEventTypes.task_finished: logger.debug( f"Received a task finish event for escrow_address={webhook.escrow_address}. " "Validating the results" diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 20986caf81..30c7149f79 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -17,7 +17,7 @@ from src.core.config import Config from src.core.manifest import TaskManifest from src.core.storage import compose_data_bucket_filename -from src.core.types import TaskType +from src.core.types import TaskTypes from src.core.validation_meta import JobMeta, ResultMeta, ValidationMeta from src.services.cloud import make_client as make_cloud_client from src.services.cloud.utils import BucketAccessInfo @@ -44,28 +44,28 @@ class ValidationFailure: DM_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_person_keypoints", - TaskType.image_boxes: "coco_instances", - TaskType.image_boxes_from_points: "coco_instances", - TaskType.image_skeletons_from_boxes: "coco_person_keypoints", + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_person_keypoints", + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", } DM_GT_DATASET_FORMAT_MAPPING = { - TaskType.image_label_binary: "cvat_images", - TaskType.image_points: "coco_instances", # we compare points against boxes - TaskType.image_boxes: "coco_instances", - TaskType.image_boxes_from_points: "coco_instances", - TaskType.image_skeletons_from_boxes: "coco_person_keypoints", + TaskTypes.image_label_binary: "cvat_images", + TaskTypes.image_points: "coco_instances", # we compare points against boxes + TaskTypes.image_boxes: "coco_instances", + TaskTypes.image_boxes_from_points: "coco_instances", + TaskTypes.image_skeletons_from_boxes: "coco_person_keypoints", } -DATASET_COMPARATOR_TYPE_MAP: Dict[TaskType, Type[DatasetComparator]] = { +DATASET_COMPARATOR_TYPE_MAP: Dict[TaskTypes, Type[DatasetComparator]] = { # TaskType.image_label_binary: TagDatasetComparator, # TODO: implement if support is needed - TaskType.image_boxes: BboxDatasetComparator, - TaskType.image_points: PointsDatasetComparator, - TaskType.image_boxes_from_points: BboxDatasetComparator, - TaskType.image_skeletons_from_boxes: SkeletonDatasetComparator, + TaskTypes.image_boxes: BboxDatasetComparator, + TaskTypes.image_points: PointsDatasetComparator, + TaskTypes.image_boxes_from_points: BboxDatasetComparator, + TaskTypes.image_skeletons_from_boxes: SkeletonDatasetComparator, } _JobResults = Dict[int, float] @@ -599,11 +599,11 @@ def process_intermediate_results( ) -> Union[ValidationSuccess, ValidationFailure]: # validate task_type = manifest.annotation.type - if task_type in [TaskType.image_label_binary, TaskType.image_boxes, TaskType.image_points]: + if task_type in [TaskTypes.image_label_binary, TaskTypes.image_boxes, TaskTypes.image_points]: validator_type = _TaskValidator - elif task_type == TaskType.image_boxes_from_points: + elif task_type == TaskTypes.image_boxes_from_points: validator_type = _BoxesFromPointsValidator - elif task_type == TaskType.image_skeletons_from_boxes: + elif task_type == TaskTypes.image_skeletons_from_boxes: validator_type = _SkeletonsFromBoxesValidator else: raise Exception(f"Unknown task type {task_type}") @@ -693,9 +693,9 @@ def put_gt_into_merged_dataset( """ match manifest.annotation.type: - case TaskType.image_boxes.value: + case TaskTypes.image_boxes.value: merged_dataset.update(gt_dataset) - case TaskType.image_points.value: + case TaskTypes.image_points.value: for sample in gt_dataset: annotations = [ # Put a point in the center of each GT bbox @@ -709,11 +709,11 @@ def put_gt_into_merged_dataset( if isinstance(bbox, dm.Bbox) ] merged_dataset.put(sample.wrap(annotations=annotations)) - case TaskType.image_label_binary.value: + case TaskTypes.image_label_binary.value: merged_dataset.update(gt_dataset) - case TaskType.image_boxes_from_points: + case TaskTypes.image_boxes_from_points: merged_dataset.update(gt_dataset) - case TaskType.image_skeletons_from_boxes: + case TaskTypes.image_skeletons_from_boxes: # The original behavior is broken for skeletons gt_dataset = dm.Dataset(gt_dataset) gt_dataset = gt_dataset.transform( diff --git a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py index e47a129847..cf898de268 100644 --- a/packages/examples/cvat/recording-oracle/src/schemas/webhook.py +++ b/packages/examples/cvat/recording-oracle/src/schemas/webhook.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, validator from src.chain.web3 import validate_address -from src.core.types import ExchangeOracleEventType, Networks +from src.core.types import ExchangeOracleEventTypes, Networks class OracleWebhook(BaseModel): @@ -24,7 +24,7 @@ class Config: "example": { "escrow_address": "0x199c44cfa6a84554ac01f3e3b01d7cfce38a75eb", "chain_id": 80001, - "event_type": ExchangeOracleEventType.task_finished.value, + "event_type": ExchangeOracleEventTypes.task_finished.value, "event_data": {}, } } diff --git a/packages/examples/cvat/recording-oracle/src/services/webhook.py b/packages/examples/cvat/recording-oracle/src/services/webhook.py index aed79da75b..31f3772c0f 100644 --- a/packages/examples/cvat/recording-oracle/src/services/webhook.py +++ b/packages/examples/cvat/recording-oracle/src/services/webhook.py @@ -18,14 +18,14 @@ from src.utils.time import utcnow -class OracleWebhookDirectionTag(str, Enum, metaclass=BetterEnumMeta): +class OracleWebhookDirectionTags(str, Enum, metaclass=BetterEnumMeta): incoming = "incoming" outgoing = "outgoing" @define class OracleWebhookQueue: - direction: OracleWebhookDirectionTag + direction: OracleWebhookDirectionTags default_sender: Optional[OracleWebhookTypes] = None def create_webhook( @@ -48,7 +48,7 @@ def create_webhook( ), f"'event' and 'event_type' cannot be used together. Please use only one of the fields" if event_type: - if self.direction == OracleWebhookDirectionTag.incoming: + if self.direction == OracleWebhookDirectionTags.incoming: sender = type else: assert self.default_sender @@ -58,9 +58,9 @@ def create_webhook( event_type = event.get_type() event_data = event.dict() - if self.direction == OracleWebhookDirectionTag.incoming and not signature: + if self.direction == OracleWebhookDirectionTags.incoming and not signature: raise ValueError("Webhook signature must be specified for incoming events") - elif self.direction == OracleWebhookDirectionTag.outgoing and signature: + elif self.direction == OracleWebhookDirectionTags.outgoing and signature: raise ValueError("Webhook signature must not be specified for outgoing events") if signature: @@ -142,8 +142,8 @@ def handle_webhook_fail(self, session: Session, webhook_id: str) -> None: session.execute(upd) -inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTag.incoming) +inbox = OracleWebhookQueue(direction=OracleWebhookDirectionTags.incoming) outbox = OracleWebhookQueue( - direction=OracleWebhookDirectionTag.outgoing, + direction=OracleWebhookDirectionTags.outgoing, default_sender=OracleWebhookTypes.recording_oracle, ) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py index 045d9a7064..f94eb61eba 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_exchange_oracle_webhooks.py @@ -11,7 +11,7 @@ from src.core.config import StorageConfig from src.core.types import ( - ExchangeOracleEventType, + ExchangeOracleEventTypes, Networks, OracleWebhookStatuses, OracleWebhookTypes, @@ -22,7 +22,7 @@ ) from src.db import SessionLocal from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags from src.utils.logging import get_function_logger from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, RECORDING_ORACLE_FEE, SIGNATURE @@ -47,13 +47,13 @@ def tearDown(self): def make_webhook(self, escrow_address): return Webhook( id=str(uuid.uuid4()), - direction=OracleWebhookDirectionTag.incoming.value, + direction=OracleWebhookDirectionTags.incoming.value, signature=SIGNATURE, escrow_address=escrow_address, chain_id=Networks.localhost.value, type=OracleWebhookTypes.exchange_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=ExchangeOracleEventType.task_finished.value, + event_type=ExchangeOracleEventTypes.task_finished.value, ) def test_process_exchange_oracle_webhook(self): diff --git a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py index 06b0b86bf7..4bb2a38ee4 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py @@ -11,12 +11,12 @@ Networks, OracleWebhookStatuses, OracleWebhookTypes, - RecordingOracleEventType, + RecordingOracleEventTypes, ) from src.crons.process_reputation_oracle_webhooks import process_outgoing_reputation_oracle_webhooks from src.db import SessionLocal from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag +from src.services.webhook import OracleWebhookDirectionTags from tests.utils.constants import DEFAULT_GAS_PAYER_PRIV, SIGNATURE from tests.utils.setup_escrow import create_escrow @@ -41,13 +41,13 @@ def tearDown(self): def get_webhook(self, escrow_address, chain_id, event_data): return Webhook( id=str(uuid.uuid4()), - direction=OracleWebhookDirectionTag.outgoing.value, + direction=OracleWebhookDirectionTags.outgoing.value, signature=SIGNATURE, escrow_address=escrow_address, chain_id=chain_id, type=OracleWebhookTypes.reputation_oracle.value, status=OracleWebhookStatuses.pending.value, - event_type=RecordingOracleEventType.task_completed, + event_type=RecordingOracleEventTypes.task_completed, event_data=event_data, ) @@ -91,7 +91,7 @@ def test_process_reputation_oracle_webhooks(self): json={ "escrowAddress": escrow_address, "chainId": chain_id, - "eventType": RecordingOracleEventType.task_completed.value, + "eventType": RecordingOracleEventTypes.task_completed.value, }, ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py b/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py index fac373dbd9..13d19f0687 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/endpoints/test_webhook.py @@ -5,7 +5,7 @@ from fastapi.testclient import TestClient from src.chain.web3 import sign_message -from src.core.types import ExchangeOracleEventType, Networks +from src.core.types import ExchangeOracleEventTypes, Networks from src.db import SessionLocal from src.endpoints.webhook import router from src.models.webhook import Webhook @@ -32,7 +32,7 @@ def test_receive_oracle_webhook_client(self, mock_get_escrow): escrow_address = "0x" + "".join([str(random.randint(0, 9)) for _ in range(40)]) chain_id = Networks.localhost - event_type = ExchangeOracleEventType.task_finished.value + event_type = ExchangeOracleEventTypes.task_finished.value message = { "escrow_address": escrow_address, diff --git a/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py b/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py index 124c1ec953..a7cf4673d4 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/services/test_webhook_service.py @@ -7,7 +7,7 @@ from src.core.types import Networks, OracleWebhookStatuses, OracleWebhookTypes from src.db import SessionLocal from src.models.webhook import Webhook -from src.services.webhook import OracleWebhookDirectionTag, inbox +from src.services.webhook import OracleWebhookDirectionTags, inbox class ServiceIntegrationTest(unittest.TestCase): @@ -30,7 +30,7 @@ def dummy_webhook(self, oracle_webhook_type: OracleWebhookTypes, status: OracleW address = "0x" + "".join([str(random.randint(0, 9)) for _ in range(40)]) return Webhook( id=str(uuid.uuid4()), - direction=OracleWebhookDirectionTag.incoming.value, + direction=OracleWebhookDirectionTags.incoming.value, signature=f"signature-{uuid.uuid4()}", escrow_address=address, chain_id=Networks.polygon_mainnet.value, From 854cbc77c728308dde81354078e1043c8b73748a Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 4 Mar 2024 17:43:31 +0200 Subject: [PATCH 59/82] Use relative paths in bucket in task creation --- .../src/handlers/job_creation.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 4e3867ae5f..ed1301ff53 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -212,6 +212,7 @@ def _download_input_data(self): points_storage_client = self._make_cloud_storage_client(points_bucket) data_filenames = data_storage_client.list_files(prefix=data_bucket.path) + data_filenames = strip_bucket_prefix(data_filenames, prefix=data_bucket.path) self._data_filenames = filter_image_files(data_filenames) self._input_gt_data = gt_storage_client.download_file(gt_bucket.path) @@ -1110,6 +1111,7 @@ def _download_input_data(self): boxes_storage_client = self._make_cloud_storage_client(boxes_bucket) data_filenames = data_storage_client.list_files(prefix=data_bucket.path) + data_filenames = strip_bucket_prefix(data_filenames, prefix=data_bucket.path) self._data_filenames = filter_image_files(data_filenames) self._input_gt_data = gt_storage_client.download_file(gt_bucket.path) @@ -2004,6 +2006,10 @@ def filter_image_files(data_filenames: List[str]) -> List[str]: return list(fn for fn in data_filenames if is_image(fn)) +def strip_bucket_prefix(data_filenames: List[str], prefix: str) -> List[str]: + return list(os.path.relpath(fn, prefix) for fn in data_filenames) + + def make_label_configuration(manifest: TaskManifest) -> List[dict]: return [ { @@ -2049,14 +2055,11 @@ def create_task(escrow_address: str, chain_id: int) -> None: gt_bucket_client = cloud_service.make_client(gt_bucket) # Task configuration creation - data_filenames = data_bucket_client.list_files( - prefix=data_bucket.path, - ) + data_filenames = data_bucket_client.list_files(prefix=data_bucket.path) + data_filenames = strip_bucket_prefix(data_filenames, prefix=data_bucket.path) data_filenames = filter_image_files(data_filenames) - gt_file_data = gt_bucket_client.download_file( - gt_bucket.path, - ) + gt_file_data = gt_bucket_client.download_file(gt_bucket.path) # Validate and parse GT gt_filenames = get_gt_filenames(gt_file_data, data_filenames, manifest=manifest) @@ -2105,7 +2108,7 @@ def create_task(escrow_address: str, chain_id: int) -> None: cvat_api.put_task_data( task.id, cloud_storage.id, - filenames=job_filenames, + filenames=[os.path.join(data_bucket.path, fn) for fn in job_filenames], sort_images=False, ) From 26c8c55a2320c69915f6376fe5ae57049fd6cfab Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 4 Mar 2024 17:44:17 +0200 Subject: [PATCH 60/82] Improve error messages --- .../src/handlers/job_creation.py | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index ed1301ff53..47a707e1f3 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -363,7 +363,7 @@ def _format_list( remainder_count = len(items) - max_items return "{}{}".format( separator.join(items[:max_items]), - f"(and {remainder_count} more)" if remainder_count > 0 else "", + f" (and {remainder_count} more)" if remainder_count > 0 else "", ) def _validate_points_categories(self): @@ -407,20 +407,14 @@ def _validate_points_filenames(self): known_data_filenames = set(self._data_filenames) matched_points_filenames = points_filenames.intersection(known_data_filenames) - if len(known_data_filenames) != len(matched_points_filenames): - missing_point_samples = list( - map(os.path.basename, known_data_filenames - matched_points_filenames) - ) + if len(matched_points_filenames) != len(points_filenames): extra_point_samples = list( map(os.path.basename, points_filenames - matched_points_filenames) ) raise MismatchingAnnotations( - "Mismatching points info and input files: {}".format( - "; ".join( - "{} missing points".format(self._format_list(missing_point_samples)), - "{} extra points".format(self._format_list(extra_point_samples)), - ) + "Failed to find several samples in the dataset files: {}".format( + self._format_list(extra_point_samples), ) ) @@ -870,7 +864,7 @@ def _extract_and_upload_rois(self): assert self._roi_filenames is not _unset src_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) - src_prefix = "" + src_prefix = src_bucket.path dst_bucket = self.oracle_data_bucket src_client = self._make_cloud_storage_client(src_bucket) @@ -1312,20 +1306,14 @@ def _validate_boxes_filenames(self): known_data_filenames = set(self._data_filenames) matched_boxes_filenames = boxes_filenames.intersection(known_data_filenames) - if len(known_data_filenames) != len(matched_boxes_filenames): - missing_box_samples = list( - map(os.path.basename, known_data_filenames - matched_boxes_filenames) - ) - extra_point_samples = list( + if len(matched_boxes_filenames) != len(boxes_filenames): + extra_bbox_samples = list( map(os.path.basename, boxes_filenames - matched_boxes_filenames) ) raise MismatchingAnnotations( - "Mismatching bbox info and input files: {}".format( - "; ".join( - "{} missing boxes".format(self._format_list(missing_box_samples)), - "{} extra boxes".format(self._format_list(extra_point_samples)), - ) + "Failed to find several samples in the dataset files: {}".format( + self._format_list(extra_bbox_samples), ) ) @@ -1405,7 +1393,7 @@ def _format_list( remainder_count = len(items) - max_items return "{}{}".format( separator.join(items[:max_items]), - f"(and {remainder_count} more)" if remainder_count > 0 else "", + f" (and {remainder_count} more)" if remainder_count > 0 else "", ) def _match_boxes(self, a: BboxCoords, b: BboxCoords): @@ -1753,7 +1741,7 @@ def _extract_and_upload_rois(self): assert self._roi_infos is not _unset src_bucket = BucketAccessInfo.parse_obj(self.manifest.data.data_url) - src_prefix = "" + src_prefix = src_bucket.path dst_bucket = self.oracle_data_bucket src_client = self._make_cloud_storage_client(src_bucket) From 019f04002f425c2c0dc904c1148b6f67c22d40c2 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 4 Mar 2024 19:46:04 +0200 Subject: [PATCH 61/82] Improve skeleton and bbox validation --- .../src/handlers/job_creation.py | 76 +++++++++++++++---- .../exchange-oracle/src/utils/annotations.py | 44 +++++++++++ 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 47a707e1f3..3e63a04de6 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -32,7 +32,7 @@ from src.log import ROOT_LOGGER_NAME from src.services.cloud import CloudProviders, StorageClient from src.services.cloud.utils import BucketAccessInfo, compose_bucket_url -from src.utils.annotations import ProjectLabels, is_point_in_bbox +from src.utils.annotations import InstanceSegmentsToBbox, ProjectLabels, is_point_in_bbox from src.utils.assignments import parse_manifest from src.utils.logging import NullLogger, get_function_logger @@ -288,6 +288,7 @@ def _validate_gt_annotations(self): excluded_gt_info = _ExcludedAnnotationsInfo() excluded_samples = set() + visited_ids = set() for gt_sample in self._gt_dataset: # Could fail on this as well img_h, img_w = gt_sample.media_as(dm.Image).size @@ -311,6 +312,15 @@ def _validate_gt_annotations(self): valid_boxes = [] break + if bbox.id in visited_ids: + excluded_gt_info.add_error( + "Sample '{}': GT bbox #{} ({}) skipped - repeated annotation id {}".format( + gt_sample.id, bbox.id, label_cat[bbox.label].name, bbox.id + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + valid_boxes.append(bbox) excluded_gt_info.excluded_count += len(sample_boxes) - len(valid_boxes) @@ -420,11 +430,12 @@ def _validate_points_filenames(self): def _validate_points_annotations(self): def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): + if skeleton.id in visited_ids: + raise DatasetValidationError(f"repeated annotation id ({skeleton.id})") + if len(skeleton.elements) != 1: raise DatasetValidationError( - "invalid points count ({}), expected 1".format( - len(skeleton.elements), - ) + "invalid points count ({}), expected 1".format(len(skeleton.elements)) ) point = skeleton.elements[0] @@ -436,6 +447,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): excluded_points_info = _ExcludedAnnotationsInfo() excluded_samples = set() + visited_ids = set() for sample in self._points_dataset: # Could fail on this as well image_h, image_w = sample.image.size @@ -1189,6 +1201,9 @@ def _validate_gt_filenames(self): def _validate_gt_annotations(self): def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): + if skeleton.id in visited_ids: + raise DatasetValidationError(f"repeated annotation id {skeleton.id}") + for element in skeleton.elements: # This is what Datumaro is expected to parse assert len(element.points) == 2 and len(element.visibility) == 1 @@ -1204,6 +1219,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): excluded_gt_info = _ExcludedAnnotationsInfo() excluded_samples = set() + visited_ids = set() for gt_sample in self._gt_dataset: # Could fail on this as well img_h, img_w = gt_sample.media_as(dm.Image).size @@ -1235,6 +1251,7 @@ def _validate_skeleton(skeleton: dm.Skeleton, *, sample_bbox: dm.Bbox): ) valid_skeletons.append(skeleton) + visited_ids.add(skeleton.id) excluded_gt_info.excluded_count += len(sample_skeletons) - len(valid_skeletons) excluded_gt_info.total_count += len(sample_skeletons) @@ -1318,10 +1335,15 @@ def _validate_boxes_filenames(self): ) def _validate_boxes_annotations(self): + # Convert possible polygons and masks into boxes + self._boxes_dataset.transform(InstanceSegmentsToBbox) + self._boxes_dataset.init_cache() + excluded_boxes_info = _ExcludedAnnotationsInfo() label_cat: dm.LabelCategories = self._boxes_dataset.categories()[dm.AnnotationType.label] + visited_ids = set() for sample in self._boxes_dataset: # Could fail on this as well image_h, image_w = sample.media_as(dm.Image).size @@ -1341,7 +1363,17 @@ def _validate_boxes_annotations(self): sample_subset=sample.subset, ) + if bbox.id in visited_ids: + excluded_boxes_info.add_error( + "Sample '{}': bbox #{} ({}) skipped - repeated annotation id {}".format( + sample.id, bbox.id, label_cat[bbox.label].name, bbox.id + ), + sample_id=sample.id, + sample_subset=sample.subset, + ) + valid_boxes.append(bbox) + visited_ids.add(bbox.id) excluded_boxes_info.excluded_count += len(sample_boxes) - len(valid_boxes) excluded_boxes_info.total_count += len(sample_boxes) @@ -1455,9 +1487,26 @@ def _prepare_gt(self): excluded_gt_info.excluded_count += 1 continue + if all( + v != dm.Points.Visibility.visible + for p in gt_skeleton.elements + for v in p.visibility + ): + # Handle fully hidden skeletons + excluded_gt_info.add_error( + "Sample '{}': GT skeleton #{} ({}) skipped - " + "no visible points".format( + gt_sample.id, gt_skeleton_id, gt_label_cat[gt_skeleton.label].name + ), + sample_id=gt_sample.id, + sample_subset=gt_sample.subset, + ) + excluded_gt_info.excluded_count += 1 + continue + matched_boxes: List[dm.Bbox] = [] for input_bbox in input_boxes: - skeleton_id = input_bbox.id + skeleton_id = gt_skeleton.id if skeleton_id in visited_skeletons: continue @@ -1516,25 +1565,26 @@ def _prepare_gt(self): updated_gt_dataset.put(gt_sample.wrap(annotations=matched_skeletons)) + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT annotations were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + if ( len(skeleton_bbox_mapping) < (1 - self.max_discarded_threshold) * excluded_gt_info.total_count ): raise DatasetValidationError( "Too many GT skeletons discarded ({} out of {}). " - "Please make sure each GT skeleton matches exactly 1 bbox".format( + "Please make sure each GT skeleton matches exactly 1 bbox " + "and has at least 1 visible point".format( excluded_gt_info.total_count - len(skeleton_bbox_mapping), excluded_gt_info.total_count, ) ) - if excluded_gt_info.excluded_count: - self.logger.warning( - "Some GT annotations were excluded due to the errors found: {}".format( - self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") - ) - ) - labels_with_few_gt = [ gt_label_cat[label_id] for label_id, label_count in gt_count_per_class.items() diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py index 94d63d55b4..1c1a843c07 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -6,6 +6,7 @@ import datumaro as dm import numpy as np from datumaro.util import filter_dict, mask_tools +from datumaro.util.annotation_util import find_group_leader, find_instances, max_bbox from defusedxml import ElementTree as ET @@ -341,3 +342,46 @@ def transform_item(self, item): def is_point_in_bbox(px: float, py: float, bbox: dm.Bbox) -> bool: return (bbox.x <= px <= bbox.x + bbox.w) and (bbox.y <= py <= bbox.y + bbox.h) + + +class InstanceSegmentsToBbox(dm.ItemTransform): + """ + Replaces instance segments (masks, polygons) with a single ("head") bbox. + A group of annotations with the same group id is considered an "instance". + The largest annotation in the group is considered the group "head". + If there is a bbox in a group, it's used as the group "head". + The resulting bbox takes properties from that "head" annotation. + """ + + def transform_item(self, item): + annotations = [] + segments = [] + for ann in item.annotations: + if ann.type in [ + dm.AnnotationType.polygon, + dm.AnnotationType.mask, + dm.AnnotationType.bbox, + ]: + segments.append(ann) + else: + annotations.append(ann) + + if not segments: + return item + + instances = find_instances(segments) + for instance_annotations in instances: + instance_leader = find_group_leader(instance_annotations) + instance_bbox = max_bbox(instance_annotations) + instance_bbox_ann = dm.Bbox( + *instance_bbox, + label=instance_leader.label, + z_order=instance_leader.z_order, + id=instance_leader.id, + attributes=instance_leader.attributes, + group=instance_leader.group, + ) + + annotations.append(instance_bbox_ann) + + return self.wrap_item(item, annotations=annotations) From 8041d62f888beb64dd1d87473994986096166214 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 5 Mar 2024 19:19:15 +0200 Subject: [PATCH 62/82] Fix escrow manifest downloading --- .../cvat/exchange-oracle/src/chain/escrow.py | 18 ++---------------- .../cvat/recording-oracle/src/chain/escrow.py | 18 ++---------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py index f44b4c2c78..8edb66c1e3 100644 --- a/packages/examples/cvat/exchange-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/exchange-oracle/src/chain/escrow.py @@ -3,10 +3,9 @@ from human_protocol_sdk.constants import ChainId, Status from human_protocol_sdk.escrow import EscrowData, EscrowUtils -from human_protocol_sdk.storage import StorageClient +from human_protocol_sdk.storage import StorageUtils from src.core.config import Config -from src.services.cloud.types import BucketAccessInfo def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: @@ -44,20 +43,7 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - bucket_info = BucketAccessInfo.parse_obj(escrow.manifest_url) - - secure = False - if bucket_info.host_url.startswith("https://"): - host = bucket_info.host_url[len("https://") :] - secure = True - elif bucket_info.host_url.startswith("http://"): - host = bucket_info.host_url[len("http://") :] - else: - host = bucket_info.host_url - - manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( - [bucket_info.path], bucket=bucket_info.bucket_name - )[0] + manifest_content = StorageUtils.download_file_from_url(escrow.manifest_url) return json.loads(manifest_content.decode("utf-8")) diff --git a/packages/examples/cvat/recording-oracle/src/chain/escrow.py b/packages/examples/cvat/recording-oracle/src/chain/escrow.py index 5169459f8e..955d51d256 100644 --- a/packages/examples/cvat/recording-oracle/src/chain/escrow.py +++ b/packages/examples/cvat/recording-oracle/src/chain/escrow.py @@ -3,10 +3,9 @@ from human_protocol_sdk.constants import ChainId, Status from human_protocol_sdk.escrow import EscrowClient, EscrowData, EscrowUtils -from human_protocol_sdk.storage import StorageClient +from human_protocol_sdk.storage import StorageUtils from src.chain.web3 import get_web3 -from src.services.cloud.types import BucketAccessInfo def get_escrow(chain_id: int, escrow_address: str) -> EscrowData: @@ -44,20 +43,7 @@ def validate_escrow( def get_escrow_manifest(chain_id: int, escrow_address: str) -> dict: escrow = get_escrow(chain_id, escrow_address) - bucket_access_info = BucketAccessInfo.parse_obj(escrow.manifest_url) - - secure = False - if bucket_access_info.host_url.startswith("https://"): - host = bucket_access_info.host_url[len("https://") :] - secure = True - elif bucket_access_info.host_url.startswith("http://"): - host = bucket_access_info.host_url[len("http://") :] - else: - host = bucket_access_info.host_url - - manifest_content = StorageClient(endpoint_url=host, secure=secure).download_files( - [bucket_access_info.path], bucket=bucket_access_info.bucket_name - )[0] + manifest_content = StorageUtils.download_file_from_url(escrow.manifest_url) return json.loads(manifest_content.decode("utf-8")) From b618f0078472d1551a3bc6083321f0c1c89dc6fb Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 5 Mar 2024 19:29:13 +0200 Subject: [PATCH 63/82] Update tests --- .../cvat/exchange-oracle/tests/integration/chain/test_escrow.py | 2 +- .../recording-oracle/tests/integration/chain/test_escrow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index 5aa045f3a3..f4ebd5a898 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -81,7 +81,7 @@ def test_validate_escrow_without_funds(self): def test_get_escrow_manifest(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function, patch( - "src.chain.escrow.StorageClient.download_files" + "src.chain.escrow.StorageUtils.download_file_from_url" ) as mock_storage: mock_storage.return_value = [json.dumps({"title": "test"}).encode()] mock_function.return_value = self.escrow_data diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index 2f45811553..1329c929db 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -88,7 +88,7 @@ def test_validate_escrow_invalid_status(self): def test_get_escrow_manifest(self): with patch("src.chain.escrow.get_escrow") as mock_get_escrow, patch( - "src.chain.escrow.StorageClient.download_files" + "src.chain.escrow.StorageUtils.download_file_from_url" ) as mock_storage: mock_storage.return_value = [json.dumps({"title": "test"}).encode()] From 77faa0c6f27276f6a5ba8b022a2e0629f2ce96bf Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 5 Mar 2024 19:29:13 +0200 Subject: [PATCH 64/82] Fix tests --- .../exchange-oracle/tests/integration/chain/test_escrow.py | 4 ++-- .../recording-oracle/tests/integration/chain/test_escrow.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py index f4ebd5a898..c3c60b931d 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/chain/test_escrow.py @@ -82,8 +82,8 @@ def test_validate_escrow_without_funds(self): def test_get_escrow_manifest(self): with patch("src.chain.escrow.EscrowUtils.get_escrow") as mock_function, patch( "src.chain.escrow.StorageUtils.download_file_from_url" - ) as mock_storage: - mock_storage.return_value = [json.dumps({"title": "test"}).encode()] + ) as mock_download: + mock_download.return_value = json.dumps({"title": "test"}).encode() mock_function.return_value = self.escrow_data manifest = get_escrow_manifest(chain_id, escrow_address) self.assertIsInstance(manifest, dict) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py index 1329c929db..4e5531db53 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/chain/test_escrow.py @@ -89,8 +89,8 @@ def test_validate_escrow_invalid_status(self): def test_get_escrow_manifest(self): with patch("src.chain.escrow.get_escrow") as mock_get_escrow, patch( "src.chain.escrow.StorageUtils.download_file_from_url" - ) as mock_storage: - mock_storage.return_value = [json.dumps({"title": "test"}).encode()] + ) as mock_download: + mock_download.return_value = json.dumps({"title": "test"}).encode() mock_get_escrow.return_value = self.escrow() manifest = get_escrow_manifest(self.network_config.chain_id, self.escrow_address) From 2f8d7f9f468c0b64ac9ef88023d9265d2c9417e9 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 8 Mar 2024 18:31:35 +0200 Subject: [PATCH 65/82] Implement general image bans --- .../versions/a0c5c3a4c13f_add_gt_stats.py | 36 ++ .../cvat/recording-oracle/src/core/config.py | 19 + .../recording-oracle/src/core/manifest.py | 2 +- .../src/core/validation_errors.py | 15 + .../src/core/validation_results.py | 16 + .../handlers/process_intermediate_results.py | 529 +++++++++++------- .../src/handlers/validation.py | 13 +- .../recording-oracle/src/models/validation.py | 19 + .../src/services/validation.py | 42 +- .../src/validation/annotation_matching.py | 2 +- .../src/validation/dataset_comparison.py | 72 ++- 11 files changed, 548 insertions(+), 217 deletions(-) create mode 100644 packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/validation_errors.py create mode 100644 packages/examples/cvat/recording-oracle/src/core/validation_results.py diff --git a/packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py b/packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py new file mode 100644 index 0000000000..eb6471554d --- /dev/null +++ b/packages/examples/cvat/recording-oracle/alembic/versions/a0c5c3a4c13f_add_gt_stats.py @@ -0,0 +1,36 @@ +"""add_gt_stats + +Revision ID: a0c5c3a4c13f +Revises: ca93dce1a618 +Create Date: 2024-03-08 11:34:02.458845 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a0c5c3a4c13f' +down_revision = 'ca93dce1a618' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('gt_stats', + sa.Column('task_id', sa.String(), nullable=False), + sa.Column('gt_key', sa.String(), nullable=False), + sa.Column('failed_attempts', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('task_id', 'gt_key') + ) + op.create_index(op.f('ix_gt_stats_gt_key'), 'gt_stats', ['gt_key'], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_gt_stats_gt_key'), table_name='gt_stats') + op.drop_table('gt_stats') + # ### end Alembic commands ### diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index 1d3759de8f..d40d0aca20 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -131,10 +131,28 @@ class FeaturesConfig: enable_custom_cloud_host = to_bool(os.environ.get("ENABLE_CUSTOM_CLOUD_HOST", "no")) "Allows using a custom host in manifest bucket urls" + +class ValidationConfig: default_point_validity_relative_radius = float( os.environ.get("DEFAULT_POINT_VALIDITY_RELATIVE_RADIUS", 0.8) ) + default_oks_sigma = float( + os.environ.get("DEFAULT_OKS_SIGMA", 0.1) # average value for COCO points + ) + "Default OKS sigma for GT skeleton points validation. Valid range is (0; 1]" + + gt_failure_threshold = float(os.environ.get("GT_FAILURE_THRESHOLD", 0.5)) + """ + The number of allowed failed assignments per GT sample + before it's considered failed for the current validation iteration + """ + + gt_ban_threshold = int(os.environ.get("GT_BAN_THRESHOLD", 3)) + """ + The number of allowed failures per GT sample before it's excluded from validation + """ + class Config: port = int(os.environ.get("PORT", 8000)) @@ -154,3 +172,4 @@ class Config: exchange_oracle_storage_config = ExchangeOracleStorageConfig features = FeaturesConfig + validation = ValidationConfig diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index a3dbef6499..c20ab628ae 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -133,7 +133,7 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: int = Field(default_factory=lambda: Config.core_config.default_assignment_time) + max_time: Optional[int] = None "Maximum time per job (assignment) for an annotator, in seconds" diff --git a/packages/examples/cvat/recording-oracle/src/core/validation_errors.py b/packages/examples/cvat/recording-oracle/src/core/validation_errors.py new file mode 100644 index 0000000000..b2d4b68a47 --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/validation_errors.py @@ -0,0 +1,15 @@ +class DatasetValidationError(Exception): + pass + + +class TooFewGtError(DatasetValidationError): + def __str__(self) -> str: + return ( + "Too many GT images were excluded. " + "Please check that all the GT images have correct annotations corresponding " + "to the dataset specification." + ) + + +class LowAccuracyError(DatasetValidationError): + pass diff --git a/packages/examples/cvat/recording-oracle/src/core/validation_results.py b/packages/examples/cvat/recording-oracle/src/core/validation_results.py new file mode 100644 index 0000000000..3a50ddea5c --- /dev/null +++ b/packages/examples/cvat/recording-oracle/src/core/validation_results.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from typing import Dict + +from src.core.validation_meta import ValidationMeta + + +@dataclass +class ValidationSuccess: + validation_meta: ValidationMeta + resulting_annotations: bytes + average_quality: float + + +@dataclass +class ValidationFailure: + rejected_jobs: Dict[int, str] diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index f202a2319c..12a1a92fbb 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -1,13 +1,15 @@ +from __future__ import annotations + import io import logging import os +from dataclasses import dataclass, field from pathlib import Path from tempfile import TemporaryDirectory -from typing import Dict, List, Optional, Sequence, Tuple, Type, TypeVar, Union +from typing import Dict, NamedTuple, Optional, Set, Type, TypeVar, Union import datumaro as dm import numpy as np -from attrs import define from sqlalchemy.orm import Session import src.core.tasks.boxes_from_points as boxes_from_points_task @@ -18,7 +20,10 @@ from src.core.manifest import TaskManifest from src.core.storage import compose_data_bucket_filename from src.core.types import TaskTypes +from src.core.validation_errors import DatasetValidationError, LowAccuracyError from src.core.validation_meta import JobMeta, ResultMeta, ValidationMeta +from src.core.validation_results import ValidationFailure, ValidationSuccess +from src.db.utils import ForUpdateParams from src.services.cloud import make_client as make_cloud_client from src.services.cloud.utils import BucketAccessInfo from src.utils.annotations import ProjectLabels, shift_ann @@ -28,21 +33,9 @@ DatasetComparator, PointsDatasetComparator, SkeletonDatasetComparator, + TooFewGtError, ) - -@define -class ValidationSuccess: - validation_meta: ValidationMeta - resulting_annotations: bytes - average_quality: float - - -@define -class ValidationFailure: - rejected_job_ids: List[int] - - DM_DATASET_FORMAT_MAPPING = { TaskTypes.image_label_binary: "cvat_images", TaskTypes.image_points: "coco_person_keypoints", @@ -69,7 +62,29 @@ class ValidationFailure: } _JobResults = Dict[int, float] -_RejectedJobs = Sequence[int] + +_RejectedJobs = Dict[int, DatasetValidationError] + +_FailedGtAttempts = Dict[str, int] +"gt key -> attempts" + + +@dataclass +class _UpdatedFailedGtInfo: + failed_jobs: Set[int] = field(default_factory=set) + occurrences: int = 0 + + +_UpdatedFailedGtStats = Dict[str, _UpdatedFailedGtInfo] + + +@dataclass +class _ValidationResult: + job_results: _JobResults + rejected_jobs: _RejectedJobs + updated_merged_dataset: io.BytesIO + updated_gt_stats: _UpdatedFailedGtStats + T = TypeVar("T") @@ -81,15 +96,22 @@ def __init__( chain_id: int, manifest: TaskManifest, *, - job_annotations: Dict[int, io.RawIOBase], - merged_annotations: Optional[io.RawIOBase] = None, + job_annotations: Dict[int, io.IOBase], + merged_annotations: io.IOBase, + gt_stats: Optional[_FailedGtAttempts] = None, ): self.escrow_address = escrow_address self.chain_id = chain_id self.manifest = manifest - self.job_annotations: Optional[Dict[int, io.IOBase]] = job_annotations - self.merged_annotations: Optional[io.IOBase] = merged_annotations + self._initial_gt_attempts: _FailedGtAttempts = gt_stats or {} + self._job_annotations: Dict[int, io.IOBase] = job_annotations + self._merged_annotations: io.IOBase = merged_annotations + + self._updated_merged_dataset_archive: Optional[io.IOBase] = None + self._updated_gt_stats: Optional[_UpdatedFailedGtStats] = None + self._job_results: Optional[_JobResults] = None + self._rejected_jobs: Optional[_RejectedJobs] = None self._temp_dir: Optional[Path] = None self._gt_dataset: Optional[dm.Dataset] = None @@ -98,6 +120,31 @@ def _require_field(self, field: Optional[T]) -> T: assert field is not None return field + def _get_gt_weight(self, failed_attempts: int) -> float: + ban_threshold = Config.validation.gt_ban_threshold + + weight = 1 + if ban_threshold < failed_attempts: + weight = 0 + + return weight + + def _get_gt_weights(self) -> Dict[str, float]: + weights = {} + + ban_threshold = Config.validation.gt_ban_threshold + if not ban_threshold: + return weights + + for gt_key, attempts in self._initial_gt_attempts.items(): + sample_id = self._gt_key_to_sample_id(gt_key) + weights[sample_id] = self._get_gt_weight(attempts) + + return weights + + def _gt_key_to_sample_id(self, gt_key: str) -> str: + return gt_key + def _parse_gt(self): tempdir = self._require_field(self._temp_dir) manifest = self._require_field(self.manifest) @@ -118,13 +165,15 @@ def _validate_jobs(self): tempdir = self._require_field(self._temp_dir) manifest = self._require_field(self.manifest) gt_dataset = self._require_field(self._gt_dataset) - job_annotations = self._require_field(self.job_annotations) + job_annotations = self._require_field(self._job_annotations) - job_results: Dict[int, float] = {} - rejected_job_ids: List[int] = [] + job_results: _JobResults = {} + rejected_jobs: _RejectedJobs = {} + updated_gt_stats: _UpdatedFailedGtStats = {} comparator = DATASET_COMPARATOR_TYPE_MAP[manifest.annotation.type]( min_similarity_threshold=manifest.validation.min_quality, + gt_weights=self._get_gt_weights(), ) for job_cvat_id, job_annotations_file in job_annotations.items(): @@ -136,19 +185,31 @@ def _validate_jobs(self): format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], ) - job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) + try: + job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) + except TooFewGtError as e: + rejected_jobs[job_cvat_id] = e + continue + job_results[job_cvat_id] = job_mean_accuracy + for gt_sample in gt_dataset: + updated_gt_stats.setdefault(gt_sample.id, _UpdatedFailedGtInfo()).occurrences += 1 + + for sample_id in comparator.failed_gts: + updated_gt_stats[sample_id].failed_jobs.add(job_cvat_id) + if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) + rejected_jobs[job_cvat_id] = LowAccuracyError() + self._updated_gt_stats = updated_gt_stats self._job_results = job_results - self._rejected_job_ids = rejected_job_ids + self._rejected_jobs = rejected_jobs def _prepare_merged_dataset(self): tempdir = self._require_field(self._temp_dir) manifest = self._require_field(self.manifest) - merged_annotations = self._require_field(self.merged_annotations) + merged_annotations = self._require_field(self._merged_annotations) gt_dataset = self._require_field(self._gt_dataset) merged_dataset_path = tempdir / "merged" @@ -208,29 +269,115 @@ def _put_gt_into_merged_dataset( case _: assert False, f"Unknown task type {manifest.annotation.type}" - def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: + def validate(self) -> _ValidationResult: with TemporaryDirectory() as tempdir: self._temp_dir = Path(tempdir) self._parse_gt() - self._validate_jobs() - job_results = self._require_field(job_results) - rejected_job_ids = self._require_field(self._rejected_job_ids) - self._prepare_merged_dataset() - updated_merged_dataset_archive = self._require_field( - self._updated_merged_dataset_archive - ) - return ( - job_results, - rejected_job_ids, - updated_merged_dataset_archive, + return _ValidationResult( + job_results=self._require_field(self._job_results), + rejected_jobs=self._require_field(self._rejected_jobs), + updated_merged_dataset=self._require_field(self._updated_merged_dataset_archive), + updated_gt_stats=self._require_field(self._updated_gt_stats), ) -class _BoxesFromPointsValidator(_TaskValidator): +class _TaskValidatorWithPerJobGt(_TaskValidator): + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: + raise NotImplementedError + + def _get_gt_weights(self, *, job_cvat_id: int, job_gt_dataset: dm.Dataset) -> Dict[str, float]: + weights = {} + + ban_threshold = Config.validation.gt_ban_threshold + if not ban_threshold: + return weights + + for gt_key, attempts in self._initial_gt_attempts.items(): + sample_id = self._gt_key_to_sample_id( + gt_key, job_cvat_id=job_cvat_id, job_gt_dataset=job_gt_dataset + ) + if not sample_id: + continue + + weights[sample_id] = self._get_gt_weight(attempts) + + return weights + + def _gt_key_to_sample_id( + self, gt_key: str, *, job_cvat_id: int, job_gt_dataset: dm.Dataset + ) -> Optional[str]: + return gt_key + + def _update_gt_stats( + self, + updated_gt_stats: _UpdatedFailedGtStats, + *, + job_cvat_id: int, + job_gt_dataset: dm.Dataset, + failed_gts: set[str], + ): + for gt_sample in job_gt_dataset: + updated_gt_stats.setdefault(gt_sample.id, _UpdatedFailedGtInfo()).occurrences += 1 + + for sample_id in failed_gts: + updated_gt_stats[sample_id].failed_jobs.add(job_cvat_id) + + return updated_gt_stats + + def _validate_jobs(self): + tempdir = self._require_field(self._temp_dir) + manifest = self._require_field(self.manifest) + job_annotations = self._require_field(self._job_annotations) + + job_results: _JobResults = {} + rejected_jobs: _RejectedJobs = {} + updated_gt_stats: _UpdatedFailedGtStats = {} + + for job_cvat_id, job_annotations_file in job_annotations.items(): + job_dataset_path = tempdir / str(job_cvat_id) + extract_zip_archive(job_annotations_file, job_dataset_path) + + job_dataset = dm.Dataset.import_from( + os.fspath(job_dataset_path), + format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + job_gt_dataset = self._make_gt_dataset_for_job(job_cvat_id, job_dataset) + + comparator = DATASET_COMPARATOR_TYPE_MAP[manifest.annotation.type]( + min_similarity_threshold=manifest.validation.min_quality, + gt_weights=self._get_gt_weights( + job_cvat_id=job_cvat_id, job_gt_dataset=job_gt_dataset + ), + ) + + try: + job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) + except TooFewGtError as e: + rejected_jobs[job_cvat_id] = e + continue + + job_results[job_cvat_id] = job_mean_accuracy + + updated_gt_stats = self._update_gt_stats( + updated_gt_stats, + job_cvat_id=job_cvat_id, + job_gt_dataset=job_gt_dataset, + failed_gts=comparator.failed_gts, + ) + + if job_mean_accuracy < manifest.validation.min_quality: + rejected_jobs[job_cvat_id] = LowAccuracyError() + + self._updated_gt_stats = updated_gt_stats + self._job_results = job_results + self._rejected_jobs = rejected_jobs + + +class _BoxesFromPointsValidator(_TaskValidatorWithPerJobGt): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -259,14 +406,14 @@ def __init__(self, *args, **kwargs): if isinstance(bbox, dm.Bbox) } - self.point_key_to_bbox_key = {v: k for k, v in boxes_to_points_mapping.items()} - self.roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} - self.roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { - os.path.splitext(roi_filename)[0]: self.roi_info_by_id[roi_id] + self._point_key_to_bbox_key = {v: k for k, v in boxes_to_points_mapping.items()} + self._roi_info_by_id = {roi_info.point_id: roi_info for roi_info in roi_infos} + self._roi_name_to_roi_info: Dict[str, boxes_from_points_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self._roi_info_by_id[roi_id] for roi_id, roi_filename in roi_filenames.items() } - self.point_offset_by_roi_id = {} + self._point_offset_by_roi_id = {} "Offset from new to old coords, (dx, dy)" for roi_info in roi_infos: @@ -280,7 +427,10 @@ def __init__(self, *args, **kwargs): old_x, old_y = old_point.points[:2] offset_x = old_x - roi_info.point_x offset_y = old_y - roi_info.point_y - self.point_offset_by_roi_id[roi_info.point_id] = (offset_x, offset_y) + self._point_offset_by_roi_id[roi_info.point_id] = (offset_x, offset_y) + + def _parse_gt(self): + pass # handled by _download_task_meta() def _download_task_meta(self): layout = boxes_from_points_task.TaskMetaLayout() @@ -331,20 +481,20 @@ def _download_task_meta(self): return boxes_to_points_mapping, roi_filenames, rois, gt_dataset, points_dataset - def _make_gt_dataset_for_job(self, job_dataset: dm.Dataset) -> dm.Dataset: + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: job_gt_dataset = dm.Dataset(categories=self._gt_dataset.categories(), media_type=dm.Image) for job_sample in job_dataset: - roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + roi_info = self._roi_name_to_roi_info[os.path.basename(job_sample.id)] - point_bbox_key = self.point_key_to_bbox_key.get(roi_info.point_id, None) + point_bbox_key = self._point_key_to_bbox_key.get(roi_info.point_id, None) if point_bbox_key is None: continue # roi is not from GT set bbox_sample = self.bbox_key_to_sample[point_bbox_key] bbox = next(bbox for bbox in bbox_sample.annotations if bbox.id == point_bbox_key) - roi_shift_x, roi_shift_y = self.point_offset_by_roi_id[roi_info.point_id] + roi_shift_x, roi_shift_y = self._point_offset_by_roi_id[roi_info.point_id] bbox_in_roi_coords = shift_ann( bbox, @@ -358,68 +508,8 @@ def _make_gt_dataset_for_job(self, job_dataset: dm.Dataset) -> dm.Dataset: return job_gt_dataset - def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: - assert self.job_annotations is not None - assert self.merged_annotations is not None - - manifest = self.manifest - task_type = manifest.annotation.type - dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - - job_annotations = self.job_annotations - merged_annotations = self.merged_annotations - - job_results: Dict[int, float] = {} - rejected_job_ids: List[int] = [] - - with TemporaryDirectory() as tempdir: - tempdir = Path(tempdir) - - comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality, - ) - - for job_cvat_id, job_annotations_file in job_annotations.items(): - job_dataset_path = tempdir / str(job_cvat_id) - extract_zip_archive(job_annotations_file, job_dataset_path) - - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), format=dataset_format - ) - job_gt_dataset = self._make_gt_dataset_for_job(job_dataset) - - job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) - job_results[job_cvat_id] = job_mean_accuracy - - if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) - - merged_dataset_path = tempdir / "merged" - merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - extract_zip_archive(merged_annotations, merged_dataset_path) - - merged_dataset = dm.Dataset.import_from( - os.fspath(merged_dataset_path), format=merged_dataset_format - ) - self._put_gt_into_merged_dataset(self._gt_dataset, merged_dataset, manifest=manifest) - updated_merged_dataset_path = tempdir / "merged_updated" - merged_dataset.export( - updated_merged_dataset_path, merged_dataset_format, save_media=False - ) - - updated_merged_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) - updated_merged_dataset_archive.seek(0) - - return ( - job_results, - rejected_job_ids, - updated_merged_dataset_archive, - ) - - -class _SkeletonsFromBoxesValidator(_TaskValidator): +class _SkeletonsFromBoxesValidator(_TaskValidatorWithPerJobGt): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -432,39 +522,39 @@ def __init__(self, *args, **kwargs): skeletons_to_boxes_mapping, ) = self._download_task_meta() - self.boxes_dataset = boxes_dataset - self.original_key_to_sample = {sample.attributes["id"]: sample for sample in boxes_dataset} + self._boxes_dataset = boxes_dataset + self._original_key_to_sample = {sample.attributes["id"]: sample for sample in boxes_dataset} - self.job_label_mapping = job_label_mapping + self._job_label_mapping = job_label_mapping - self.gt_dataset = gt_dataset + self._gt_dataset = gt_dataset - self.bbox_key_to_sample = { + self._bbox_key_to_sample = { bbox.id: sample for sample in boxes_dataset for bbox in sample.annotations if isinstance(bbox, dm.Bbox) } - self.skeleton_key_to_sample = { + self._skeleton_key_to_sample = { skeleton.id: sample for sample in gt_dataset for skeleton in sample.annotations if isinstance(skeleton, dm.Skeleton) } - self.bbox_key_to_skeleton_key = {v: k for k, v in skeletons_to_boxes_mapping.items()} - self.roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in roi_infos} - self.roi_name_to_roi_info: Dict[str, skeletons_from_boxes_task.RoiInfo] = { - os.path.splitext(roi_filename)[0]: self.roi_info_by_id[roi_id] + self._bbox_key_to_skeleton_key = {v: k for k, v in skeletons_to_boxes_mapping.items()} + self._roi_info_by_id = {roi_info.bbox_id: roi_info for roi_info in roi_infos} + self._roi_name_to_roi_info: Dict[str, skeletons_from_boxes_task.RoiInfo] = { + os.path.splitext(roi_filename)[0]: self._roi_info_by_id[roi_id] for roi_id, roi_filename in roi_filenames.items() } - self.bbox_offset_by_roi_id = {} + self._bbox_offset_by_roi_id = {} "Offset from old to new coords, (dx, dy)" for roi_info in roi_infos: - bbox_sample = self.bbox_key_to_sample[roi_info.bbox_id] + bbox_sample = self._bbox_key_to_sample[roi_info.bbox_id] old_bbox = next( bbox @@ -475,7 +565,10 @@ def __init__(self, *args, **kwargs): offset_x = roi_info.bbox_x - old_bbox.x offset_y = roi_info.bbox_y - old_bbox.y - self.bbox_offset_by_roi_id[roi_info.bbox_id] = (offset_x, offset_y) + self._bbox_offset_by_roi_id[roi_info.bbox_id] = (offset_x, offset_y) + + def _parse_gt(self): + pass # handled by _download_task_meta() def _download_task_meta(self): layout = skeletons_from_boxes_task.TaskMetaLayout() @@ -551,20 +644,20 @@ def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.D (i, c) for i, c in enumerate(job_label_cat) if c.parent ) - gt_label_cat = self.gt_dataset.categories()[dm.AnnotationType.label] + gt_label_cat = self._gt_dataset.categories()[dm.AnnotationType.label] gt_point_label_id = gt_label_cat.find(job_point_label.name, parent=job_skeleton_label.name)[ 0 ] job_gt_dataset = dm.Dataset(categories=job_dataset.categories(), media_type=dm.Image) for job_sample in job_dataset: - roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + roi_info = self._roi_name_to_roi_info[os.path.basename(job_sample.id)] - gt_skeleton_key = self.bbox_key_to_skeleton_key.get(roi_info.bbox_id, None) + gt_skeleton_key = self._bbox_key_to_skeleton_key.get(roi_info.bbox_id, None) if gt_skeleton_key is None: continue # roi is not from GT set - bbox_sample = self.bbox_key_to_sample[roi_info.bbox_id] + bbox_sample = self._bbox_key_to_sample[roi_info.bbox_id] bbox = next( bbox for bbox in bbox_sample.annotations @@ -572,7 +665,7 @@ def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.D if isinstance(bbox, dm.Bbox) ) - gt_sample = self.skeleton_key_to_sample[gt_skeleton_key] + gt_sample = self._skeleton_key_to_sample[gt_skeleton_key] gt_skeleton = next( skeleton for skeleton in gt_sample.annotations @@ -580,7 +673,7 @@ def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.D if isinstance(skeleton, dm.Skeleton) ) - roi_shift_x, roi_shift_y = self.bbox_offset_by_roi_id[roi_info.bbox_id] + roi_shift_x, roi_shift_y = self._bbox_offset_by_roi_id[roi_info.bbox_id] converted_gt_skeleton = shift_ann( gt_skeleton, offset_x=roi_shift_x, @@ -613,65 +706,101 @@ def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.D return job_gt_dataset - def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: - assert self.job_annotations is not None - assert self.merged_annotations is not None + @dataclass + class _GtKey: + sample_id: str + skeleton_id: int + point_id: int - manifest = self.manifest - task_type = manifest.annotation.type - dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] + _GT_KEY_SEPARATOR = ":" - job_annotations = self.job_annotations - merged_annotations = self.merged_annotations + def _parse_gt_key(self, raw_gt_key: str) -> _GtKey: + # Assume "sample_id:skeleton_id:point_id" + sample_id, skeleton_id, point_id = raw_gt_key.rsplit(self._GT_KEY_SEPARATOR, maxsplit=2) - job_results: Dict[int, float] = {} - rejected_job_ids: List[int] = [] + return self._GtKey( + sample_id=sample_id, skeleton_id=int(skeleton_id), point_id=int(point_id) + ) - with TemporaryDirectory() as tempdir: - tempdir = Path(tempdir) + def _serialize_gt_key(self, parsed_gt_key: _GtKey) -> str: + # Assume "sample_id:skeleton_id:point_id" + return self._GT_KEY_SEPARATOR.join( + [parsed_gt_key.sample_id, str(parsed_gt_key.skeleton_id), str(parsed_gt_key.point_id)] + ) - comparator = DATASET_COMPARATOR_TYPE_MAP[task_type]( - min_similarity_threshold=manifest.validation.min_quality + class _LabelId(NamedTuple): + skeleton_id: int + point_id: int + + def _get_gt_dataset_label_id(self, job_gt_dataset: dm.Dataset) -> _LabelId: + label_cat: dm.LabelCategories = job_gt_dataset.categories()[dm.AnnotationType.label] + assert len(label_cat) == 2 + job_skeleton_label = next(l for l in label_cat if not l.parent) + job_point_label = next(l for l in label_cat if l.parent) + + return self._LabelId( + *next( + (skeleton_id, point_id) + for skeleton_id, skeleton_label in enumerate(self.manifest.annotation.labels) + for point_id, point_name in enumerate(skeleton_label.nodes) + if skeleton_label.name == job_skeleton_label.name + if point_name == job_point_label.name ) + ) - for job_cvat_id, job_annotations_file in job_annotations.items(): - job_dataset_path = tempdir / str(job_cvat_id) - extract_zip_archive(job_annotations_file, job_dataset_path) + def _gt_key_to_sample_id( + self, gt_key: str, *, job_cvat_id: int, job_gt_dataset: dm.Dataset + ) -> Optional[str]: + parsed_gt_key = self._parse_gt_key(gt_key) + job_label_id = self._get_gt_dataset_label_id(job_gt_dataset) + if (parsed_gt_key.skeleton_id, parsed_gt_key.point_id) != job_label_id: + return None - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), format=dataset_format - ) - job_gt_dataset = self._make_gt_dataset_for_job(job_cvat_id, job_dataset) + return parsed_gt_key.sample_id - job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) - job_results[job_cvat_id] = job_mean_accuracy - - if job_mean_accuracy < manifest.validation.min_quality: - rejected_job_ids.append(job_cvat_id) + def _update_gt_stats( + self, + updated_gt_stats: _UpdatedFailedGtStats, + *, + job_cvat_id: int, + job_gt_dataset: dm.Dataset, + failed_gts: set[str], + ): + job_label_id = self._get_gt_dataset_label_id(job_gt_dataset) + + for gt_sample in job_gt_dataset: + raw_gt_key = self._serialize_gt_key( + self._GtKey( + sample_id=gt_sample.id, + skeleton_id=job_label_id.skeleton_id, + point_id=job_label_id.point_id, + ) + ) + updated_gt_stats.setdefault(raw_gt_key, _UpdatedFailedGtInfo()).occurrences += 1 + + for gt_sample_id in failed_gts: + raw_gt_key = self._serialize_gt_key( + self._GtKey( + sample_id=gt_sample_id, + skeleton_id=job_label_id.skeleton_id, + point_id=job_label_id.point_id, + ) + ) + updated_gt_stats[raw_gt_key].failed_jobs.add(job_cvat_id) - merged_dataset_path = tempdir / "merged" - merged_dataset_format = DM_DATASET_FORMAT_MAPPING[task_type] - extract_zip_archive(merged_annotations, merged_dataset_path) + return updated_gt_stats - merged_dataset = dm.Dataset.import_from( - os.fspath(merged_dataset_path), format=merged_dataset_format - ) - self._put_gt_into_merged_dataset(self.gt_dataset, merged_dataset, manifest=manifest) - updated_merged_dataset_path = tempdir / "merged_updated" - merged_dataset.export( - updated_merged_dataset_path, merged_dataset_format, save_media=False - ) +def _compute_gt_stats_update( + initial_gt_stats: _FailedGtAttempts, validation_gt_stats: _UpdatedFailedGtStats +) -> _FailedGtAttempts: + updated_gt_stats = initial_gt_stats.copy() - updated_merged_dataset_archive = io.BytesIO() - write_dir_to_zip_archive(updated_merged_dataset_path, updated_merged_dataset_archive) - updated_merged_dataset_archive.seek(0) + for gt_key, gt_info in validation_gt_stats.items(): + if gt_info.occurrences * Config.validation.gt_failure_threshold < len(gt_info.failed_jobs): + updated_gt_stats[gt_key] = updated_gt_stats.get(gt_key, 0) + 1 - return ( - job_results, - rejected_job_ids, - updated_merged_dataset_archive, - ) + return updated_gt_stats def process_intermediate_results( @@ -685,7 +814,8 @@ def process_intermediate_results( manifest: TaskManifest, logger: logging.Logger, ) -> Union[ValidationSuccess, ValidationFailure]: - # validate + # actually validate jobs + task_type = manifest.annotation.type if task_type in [TaskTypes.image_label_binary, TaskTypes.image_boxes, TaskTypes.image_points]: validator_type = _TaskValidator @@ -696,14 +826,35 @@ def process_intermediate_results( else: raise Exception(f"Unknown task type {task_type}") + task = db_service.get_task_by_escrow_address( + session, + escrow_address, + for_update=ForUpdateParams( + nowait=True + ), # should not happen, but waiting should not block processing + ) + if not task: + task_id = db_service.create_task(session, escrow_address=escrow_address, chain_id=chain_id) + task = db_service.get_task_by_id(session, task_id, for_update=True) + + initial_gt_stats = { + gt_image_stat.gt_key: gt_image_stat.failed_attempts + for gt_image_stat in db_service.get_task_gt_stats(session, task.id) + } + validator = validator_type( escrow_address=escrow_address, chain_id=chain_id, manifest=manifest, job_annotations=job_annotations, merged_annotations=merged_annotations, + gt_stats=initial_gt_stats, ) - job_results, rejected_job_ids, updated_merged_dataset_archive = validator.validate() + + validation_result = validator.validate() + job_results = validation_result.job_results + rejected_jobs = validation_result.rejected_jobs + updated_merged_dataset_archive = validation_result.updated_merged_dataset if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -712,10 +863,11 @@ def process_intermediate_results( ", ".join(f"{k}: {v:.2f}" for k, v in job_results.items()), ) - task = db_service.get_task_by_escrow_address(session, escrow_address, for_update=True) - if not task: - task_id = db_service.create_task(session, escrow_address=escrow_address, chain_id=chain_id) - task = db_service.get_task_by_id(session, task_id, for_update=True) + if validation_result.updated_gt_stats: + updated_gt_stats = _compute_gt_stats_update( + initial_gt_stats, validation_result.updated_gt_stats + ) + db_service.update_gt_stats(session, task.id, updated_gt_stats) job_final_result_ids: Dict[int, str] = {} for job_meta in meta.jobs: @@ -724,11 +876,11 @@ def process_intermediate_results( job_id = db_service.create_job(session, task_id=task.id, job_cvat_id=job_meta.job_id) job = db_service.get_job_by_id(session, job_id) - validation_result = db_service.get_validation_result_by_assignment_id( + assignment_validation_result = db_service.get_validation_result_by_assignment_id( session, job_meta.assignment_id ) - if not validation_result: - validation_result_id = db_service.create_validation_result( + if not assignment_validation_result: + assignment_validation_result_id = db_service.create_validation_result( session, job_id=job.id, annotator_wallet_address=job_meta.annotator_wallet_address, @@ -736,12 +888,12 @@ def process_intermediate_results( assignment_id=job_meta.assignment_id, ) else: - validation_result_id = validation_result.id + assignment_validation_result_id = assignment_validation_result.id - job_final_result_ids[job.id] = validation_result_id + job_final_result_ids[job.id] = assignment_validation_result_id - if rejected_job_ids: - return ValidationFailure(rejected_job_ids) + if rejected_jobs: + return ValidationFailure(rejected_jobs) task_jobs = task.jobs task_validation_results = db_service.get_task_validation_results(session, task.id) @@ -775,6 +927,7 @@ def process_intermediate_results( average_quality=np.mean(list(job_results.values())) if job_results else 0, ) + def parse_annotation_metafile(metafile: io.RawIOBase) -> AnnotationMeta: return AnnotationMeta.parse_raw(metafile.read()) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index 86cf812452..8f7af77fa0 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -1,5 +1,6 @@ import io import os +from collections import Counter from logging import Logger from typing import Dict, Optional, Union @@ -19,8 +20,8 @@ compose_results_bucket_filename as compose_annotation_results_bucket_filename, ) from src.core.types import OracleWebhookTypes +from src.core.validation_results import ValidationFailure, ValidationSuccess from src.handlers.process_intermediate_results import ( - ValidationSuccess, parse_annotation_metafile, process_intermediate_results, serialize_validation_meta, @@ -30,7 +31,6 @@ from src.services.cloud.utils import BucketAccessInfo from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest from src.utils.logging import NullLogger, get_function_logger -from validators import ValidationFailure module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" @@ -177,9 +177,13 @@ def _handle_validation_result(self, validation_result: ValidationResult): event=RecordingOracleEvent_TaskCompleted(), ) else: + error_type_counts = Counter( + type(e).__name__ for e in validation_result.rejected_jobs.values() + ) logger.info( f"Validation for escrow_address={escrow_address} failed, " - f"rejected {len(validation_result.rejected_job_ids)} jobs" + f"rejected {len(validation_result.rejected_jobs)} jobs. " + f"Problems: {dict(error_type_counts)}" ) oracle_db_service.outbox.create_webhook( @@ -188,7 +192,8 @@ def _handle_validation_result(self, validation_result: ValidationResult): chain_id, OracleWebhookTypes.exchange_oracle, event=RecordingOracleEvent_TaskRejected( - rejected_job_ids=validation_result.rejected_job_ids + # TODO: update wrt. M2 API changes + rejected_job_ids=list(validation_result.rejected_jobs) ), ) diff --git a/packages/examples/cvat/recording-oracle/src/models/validation.py b/packages/examples/cvat/recording-oracle/src/models/validation.py index 7945ac28da..aa75e93c2d 100644 --- a/packages/examples/cvat/recording-oracle/src/models/validation.py +++ b/packages/examples/cvat/recording-oracle/src/models/validation.py @@ -22,6 +22,9 @@ class Task(Base): jobs: Mapped[List["Job"]] = relationship( back_populates="task", cascade="all, delete", passive_deletes=True ) + gt_stats: Mapped[List["GtStats"]] = relationship( + back_populates="task", cascade="all, delete", passive_deletes=True + ) class Job(Base): @@ -45,3 +48,19 @@ class ValidationResult(Base): annotation_quality = Column(Float, nullable=False) job: Mapped["Job"] = relationship(back_populates="validation_results") + + +class GtStats(Base): + __tablename__ = "gt_stats" + + # A composite primary key is used + task_id = Column( + String, ForeignKey("tasks.id", ondelete="CASCADE"), primary_key=True, nullable=False + ) + + # TODO: think how to store this better + gt_key = Column(String, index=True, primary_key=True, nullable=False) + + failed_attempts = Column(Integer, default=0, nullable=False) + + task: Mapped["Task"] = relationship(back_populates="gt_stats") diff --git a/packages/examples/cvat/recording-oracle/src/services/validation.py b/packages/examples/cvat/recording-oracle/src/services/validation.py index c1aa577219..ed5c8c2149 100644 --- a/packages/examples/cvat/recording-oracle/src/services/validation.py +++ b/packages/examples/cvat/recording-oracle/src/services/validation.py @@ -1,11 +1,12 @@ import uuid -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union from sqlalchemy.orm import Session +from db import engine as db_engine from src.db.utils import ForUpdateParams from src.db.utils import maybe_for_update as _maybe_for_update -from src.models.validation import Job, Task, ValidationResult +from src.models.validation import GtStats, Job, Task, ValidationResult def create_task(session: Session, escrow_address: str, chain_id: int) -> str: @@ -99,3 +100,40 @@ def get_validation_result_by_assignment_id( .where(ValidationResult.assignment_id == assignment_id) .first() ) + + +def get_task_gt_stats( + session: Session, task_id: str, *, for_update: Union[bool, ForUpdateParams] = False +) -> List[GtStats]: + return ( + _maybe_for_update(session.query(GtStats), enable=for_update) + .where(GtStats.task_id == task_id) + .all() + ) + + +def update_gt_stats(session: Session, task_id: str, values: Dict[str, int]): + # Read more about upsert: + # https://docs.sqlalchemy.org/en/20/orm/queryguide/dml.html#orm-upsert-statements + + if db_engine.driver != "psycopg2": + raise NotImplementedError + + from sqlalchemy.dialects.postgresql import insert as psql_insert + from sqlalchemy.inspection import inspect + + statement = psql_insert(GtStats).values( + [ + dict( + task_id=task_id, + gt_key=gt_key, + failed_attempts=failed_attempts, + ) + for gt_key, failed_attempts in values.items() + ], + ) + statement = statement.on_conflict_do_update( + index_elements=inspect(GtStats).primary_key, set_=statement.excluded + ) + + session.execute(statement) diff --git a/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py b/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py index 70d4817cd6..fa3a965f7f 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py +++ b/packages/examples/cvat/recording-oracle/src/validation/annotation_matching.py @@ -52,7 +52,7 @@ def point_to_bbox_cmp( bbox: Bbox, point: Point, *, - rel_sigma: float = Config.features.default_point_validity_relative_radius, + rel_sigma: float = Config.validation.default_point_validity_relative_radius, ) -> float: """ Checks that the point is within the axis-aligned bbox, diff --git a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py index 787b080962..6a570dc663 100644 --- a/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py +++ b/packages/examples/cvat/recording-oracle/src/validation/dataset_comparison.py @@ -6,9 +6,12 @@ import datumaro as dm import numpy as np -from attrs import define +from attrs import define, field from datumaro.util.annotation_util import BboxCoords +from src.core.config import Config +from src.core.validation_errors import TooFewGtError + from .annotation_matching import ( Bbox, MatchResult, @@ -52,11 +55,17 @@ def clear_cache(self): @define class DatasetComparator(metaclass=ABCMeta): - min_similarity_threshold: float + _min_similarity_threshold: float + _gt_weights: Dict[str, float] = field(factory=dict) + + failed_gts: Set[str] = field(factory=set, init=False) + "Recorded list of failed GT samples, available after compare() call" def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: dataset_similarities = [] dataset_total_anns_to_compare = 0 + dataset_failed_gts = set() + dataset_excluded_gts_count = 0 for ds_sample in ds_dataset: gt_sample = gt_dataset.get(ds_sample.id) @@ -64,7 +73,15 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: if not gt_sample: continue - matching_result, similarity_fn = self.compare_sample_annotations(gt_sample, ds_sample) + sample_weight = self._gt_weights.get(gt_sample.id, 1) + if not sample_weight: + dataset_excluded_gts_count += 1 + continue + + sample_similarity_threshold = self._min_similarity_threshold * sample_weight + matching_result, similarity_fn = self.compare_sample_annotations( + gt_sample, ds_sample, similarity_threshold=sample_similarity_threshold + ) sample_similarities = [] sample_total_anns_to_compare = 0 @@ -81,22 +98,34 @@ def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: dataset_similarities.extend(sample_similarities) dataset_total_anns_to_compare += sample_total_anns_to_compare + sample_accuracy = 0 + if sample_total_anns_to_compare: + sample_accuracy = 2 * np.sum(sample_similarities) / sample_total_anns_to_compare + + if sample_accuracy < sample_similarity_threshold: + dataset_failed_gts.add(gt_sample.id) + + if dataset_excluded_gts_count == len(gt_dataset): + raise TooFewGtError() + dataset_accuracy = 0 if dataset_total_anns_to_compare: dataset_accuracy = 2 * np.sum(dataset_similarities) / dataset_total_anns_to_compare + self.failed_gts = dataset_failed_gts + return dataset_accuracy @abstractmethod def compare_sample_annotations( - self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float ) -> Tuple[MatchResult, SimilarityFunction]: ... class BboxDatasetComparator(DatasetComparator): def compare_sample_annotations( - self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float ) -> Tuple[MatchResult, SimilarityFunction]: similarity_fn = CachedSimilarityFunction(bbox_iou) @@ -115,7 +144,7 @@ def compare_sample_annotations( gt_boxes, ds_boxes, similarity=similarity_fn, - min_similarity=self.min_similarity_threshold, + min_similarity=similarity_threshold, ) return matching_result, similarity_fn @@ -123,7 +152,7 @@ def compare_sample_annotations( class PointsDatasetComparator(DatasetComparator): def compare_sample_annotations( - self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float ) -> Tuple[MatchResult, SimilarityFunction]: similarity_fn = CachedSimilarityFunction(point_to_bbox_cmp) @@ -146,32 +175,33 @@ def compare_sample_annotations( gt_boxes, ds_points, similarity=similarity_fn, - min_similarity=self.min_similarity_threshold, + min_similarity=similarity_threshold, ) return matching_result, similarity_fn -class SkeletonDatasetComparator(DatasetComparator): - _SkeletonInfo = list[str] +_SkeletonInfo = list[str] - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._skeleton_info: Dict[int, self._SkeletonInfo] = {} - self._categories: Optional[dm.CategoriesInfo] = None +@define +class SkeletonDatasetComparator(DatasetComparator): + _skeleton_info: Dict[int, _SkeletonInfo] = field(factory=dict, init=False) + _categories: Optional[dm.CategoriesInfo] = field(default=None, init=False) - # TODO: find better strategy for sigma estimation - self.oks_sigma = 0.1 # average value for COCO points + # TODO: find better strategy for sigma estimation + _oks_sigma: float = Config.validation.default_oks_sigma def compare(self, gt_dataset: dm.Dataset, ds_dataset: dm.Dataset) -> float: self._categories = gt_dataset.categories() return super().compare(gt_dataset, ds_dataset) def compare_sample_annotations( - self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem + self, gt_sample: dm.DatasetItem, ds_sample: dm.DatasetItem, *, similarity_threshold: float ) -> Tuple[MatchResult, SimilarityFunction]: - return self._match_skeletons(gt_sample, ds_sample) + return self._match_skeletons( + gt_sample, ds_sample, similarity_threshold=similarity_threshold + ) def _get_skeleton_info(self, skeleton_label_id: int) -> _SkeletonInfo: label_cat: dm.LabelCategories = self._categories[dm.AnnotationType.label] @@ -189,7 +219,7 @@ def _get_skeleton_info(self, skeleton_label_id: int) -> _SkeletonInfo: return skeleton_info def _match_skeletons( - self, item_a: dm.DatasetItem, item_b: dm.DatasetItem + self, item_a: dm.DatasetItem, item_b: dm.DatasetItem, *, similarity_threshold: float ) -> Tuple[MatchResult, SimilarityFunction]: a_skeletons = [a for a in item_a.annotations if isinstance(a, dm.Skeleton)] b_skeletons = [a for a in item_b.annotations if isinstance(a, dm.Skeleton)] @@ -243,13 +273,13 @@ def _match_skeletons( for ann in instance_group: instance_map[id(ann)] = [instance_group, instance_bbox] - keypoints_matcher = self._KeypointsMatcher(instance_map=instance_map, sigma=self.oks_sigma) + keypoints_matcher = self._KeypointsMatcher(instance_map=instance_map, sigma=self._oks_sigma) keypoints_similarity = CachedSimilarityFunction(keypoints_matcher.distance) matching_result = match_annotations( a_points, b_points, similarity=keypoints_similarity, - min_similarity=self.min_similarity_threshold, + min_similarity=similarity_threshold, ) distances = keypoints_similarity.cache From 57bd47a2fe88e61f465fdac4b7308d0e1d8d413d Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 8 Mar 2024 18:39:53 +0200 Subject: [PATCH 66/82] Fix import --- .../examples/cvat/recording-oracle/src/services/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/examples/cvat/recording-oracle/src/services/validation.py b/packages/examples/cvat/recording-oracle/src/services/validation.py index ed5c8c2149..f4a054e7fc 100644 --- a/packages/examples/cvat/recording-oracle/src/services/validation.py +++ b/packages/examples/cvat/recording-oracle/src/services/validation.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import Session -from db import engine as db_engine +from src.db import engine as db_engine from src.db.utils import ForUpdateParams from src.db.utils import maybe_for_update as _maybe_for_update from src.models.validation import GtStats, Job, Task, ValidationResult From f88896dc97bc5a5a2ec1fab9f38ede6327cbf2bb Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 11 Mar 2024 14:47:10 +0200 Subject: [PATCH 67/82] Make default gt ban threshold more strict --- .../examples/cvat/recording-oracle/src/core/config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index d40d0aca20..aa30d33f1b 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -142,10 +142,12 @@ class ValidationConfig: ) "Default OKS sigma for GT skeleton points validation. Valid range is (0; 1]" - gt_failure_threshold = float(os.environ.get("GT_FAILURE_THRESHOLD", 0.5)) + gt_failure_threshold = float(os.environ.get("GT_FAILURE_THRESHOLD", 0.9)) """ - The number of allowed failed assignments per GT sample - before it's considered failed for the current validation iteration + The fraction of allowed failed assignments per GT sample, + before it's considered failed for the current validation iteration. + v = 0 -> any GT failure leads to image failure + v = 1 -> any GT failures do not lead to image failure """ gt_ban_threshold = int(os.environ.get("GT_BAN_THRESHOLD", 3)) From 6570d8985849c246a5e63413a0bfc261fdf783a6 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 11 Mar 2024 19:09:06 +0200 Subject: [PATCH 68/82] Fix comparison for absent points --- .../exchange-oracle/src/utils/annotations.py | 8 +-- .../handlers/process_intermediate_results.py | 70 +++++++++++++++++-- 2 files changed, 67 insertions(+), 11 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py index 94d63d55b4..06e445d7f3 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -1,7 +1,7 @@ import os from copy import deepcopy from glob import glob -from typing import Dict, Iterable, List, Optional, Sequence, Tuple, Union +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union import datumaro as dm import numpy as np @@ -139,9 +139,9 @@ def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str] return dataset -def shift_ann( - ann: dm.Annotation, offset_x: float, offset_y: float, *, img_w: int, img_h: int -) -> dm.Annotation: +T = TypeVar("T", bound=dm.Annotation) + +def shift_ann(ann: T, offset_x: float, offset_y: float, *, img_w: int, img_h: int) -> T: "Shift annotation coordinates with clipping to the image size" if isinstance(ann, dm.Bbox): diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index f202a2319c..b0c73ac333 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -114,6 +114,14 @@ def _parse_gt(self): format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], ) + def _load_job_dataset(self, job_id: int, job_dataset_path: Path) -> dm.Dataset: + manifest = self._require_field(self.manifest) + + return dm.Dataset.import_from( + os.fspath(job_dataset_path), + format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + def _validate_jobs(self): tempdir = self._require_field(self._temp_dir) manifest = self._require_field(self.manifest) @@ -131,10 +139,7 @@ def _validate_jobs(self): job_dataset_path = tempdir / str(job_cvat_id) extract_zip_archive(job_annotations_file, job_dataset_path) - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), - format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], - ) + job_dataset = self._load_job_dataset(job_cvat_id, job_dataset_path) job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) job_results[job_cvat_id] = job_mean_accuracy @@ -383,9 +388,7 @@ def validate(self) -> Tuple[_JobResults, _RejectedJobs, io.BytesIO]: job_dataset_path = tempdir / str(job_cvat_id) extract_zip_archive(job_annotations_file, job_dataset_path) - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), format=dataset_format - ) + job_dataset = self._load_job_dataset(job_cvat_id, job_dataset_path) job_gt_dataset = self._make_gt_dataset_for_job(job_dataset) job_mean_accuracy = comparator.compare(job_gt_dataset, job_dataset) @@ -541,6 +544,59 @@ def _download_task_meta(self): skeletons_to_boxes_mapping, ) + def _load_job_dataset(self, job_id: int, job_dataset_path: Path) -> dm.Dataset: + job_dataset = super()._load_job_dataset(job_id=job_id, job_dataset_path=job_dataset_path) + + cat = job_dataset.categories() + updated_dataset = dm.Dataset(categories=cat, media_type=job_dataset.media_type()) + + job_label_cat: dm.LabelCategories = cat[dm.AnnotationType.label] + assert len(job_label_cat) == 2 + job_skeleton_label_id = next(i for i, c in enumerate(job_label_cat) if not c.parent) + job_point_label_id = next(i for i, c in enumerate(job_label_cat) if c.parent) + + for job_sample in job_dataset: + updated_annotations = job_sample.annotations.copy() + + if not job_sample.annotations: + skeleton = dm.Skeleton( + label=job_skeleton_label_id, + elements=[ + dm.Points( + [0, 0], + visibility=[dm.Points.Visibility.absent], + label=job_point_label_id, + ) + ], + ) + + roi_info = self.roi_name_to_roi_info[os.path.basename(job_sample.id)] + bbox_sample = self.bbox_key_to_sample[roi_info.bbox_id] + bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + + roi_shift_x, roi_shift_y = self.bbox_offset_by_roi_id[roi_info.bbox_id] + converted_bbox = shift_ann( + bbox, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + skeleton.group = 1 + converted_bbox.group = skeleton.group + + updated_annotations = [skeleton, converted_bbox] + + updated_dataset.put(job_sample.wrap(annotations=updated_annotations)) + + return updated_dataset + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: job_label_cat: dm.LabelCategories = job_dataset.categories()[dm.AnnotationType.label] assert len(job_label_cat) == 2 From 6bf2aa8323fec62dbcbb5c25d0ec8f8b22257ebd Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 11 Mar 2024 19:13:18 +0200 Subject: [PATCH 69/82] Fix comparison for omitted points in jobs --- .../handlers/process_intermediate_results.py | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 12a1a92fbb..3cea860561 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -161,6 +161,14 @@ def _parse_gt(self): format=DM_GT_DATASET_FORMAT_MAPPING[manifest.annotation.type], ) + def _load_job_dataset(self, job_id: int, job_dataset_path: Path) -> dm.Dataset: + manifest = self._require_field(self.manifest) + + return dm.Dataset.import_from( + os.fspath(job_dataset_path), + format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], + ) + def _validate_jobs(self): tempdir = self._require_field(self._temp_dir) manifest = self._require_field(self.manifest) @@ -180,10 +188,7 @@ def _validate_jobs(self): job_dataset_path = tempdir / str(job_cvat_id) extract_zip_archive(job_annotations_file, job_dataset_path) - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), - format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], - ) + job_dataset = self._load_job_dataset(job_cvat_id, job_dataset_path) try: job_mean_accuracy = comparator.compare(gt_dataset, job_dataset) @@ -341,10 +346,7 @@ def _validate_jobs(self): job_dataset_path = tempdir / str(job_cvat_id) extract_zip_archive(job_annotations_file, job_dataset_path) - job_dataset = dm.Dataset.import_from( - os.fspath(job_dataset_path), - format=DM_DATASET_FORMAT_MAPPING[manifest.annotation.type], - ) + job_dataset = self._load_job_dataset(job_cvat_id, job_dataset_path) job_gt_dataset = self._make_gt_dataset_for_job(job_cvat_id, job_dataset) comparator = DATASET_COMPARATOR_TYPE_MAP[manifest.annotation.type]( @@ -634,6 +636,59 @@ def _download_task_meta(self): skeletons_to_boxes_mapping, ) + def _load_job_dataset(self, job_id: int, job_dataset_path: Path) -> dm.Dataset: + job_dataset = super()._load_job_dataset(job_id=job_id, job_dataset_path=job_dataset_path) + + cat = job_dataset.categories() + updated_dataset = dm.Dataset(categories=cat, media_type=job_dataset.media_type()) + + job_label_cat: dm.LabelCategories = cat[dm.AnnotationType.label] + assert len(job_label_cat) == 2 + job_skeleton_label_id = next(i for i, c in enumerate(job_label_cat) if not c.parent) + job_point_label_id = next(i for i, c in enumerate(job_label_cat) if c.parent) + + for job_sample in job_dataset: + updated_annotations = job_sample.annotations.copy() + + if not job_sample.annotations: + skeleton = dm.Skeleton( + label=job_skeleton_label_id, + elements=[ + dm.Points( + [0, 0], + visibility=[dm.Points.Visibility.absent], + label=job_point_label_id, + ) + ], + ) + + roi_info = self._roi_name_to_roi_info[os.path.basename(job_sample.id)] + bbox_sample = self._bbox_key_to_sample[roi_info.bbox_id] + bbox = next( + bbox + for bbox in bbox_sample.annotations + if bbox.id == roi_info.bbox_id + if isinstance(bbox, dm.Bbox) + ) + + roi_shift_x, roi_shift_y = self._bbox_offset_by_roi_id[roi_info.bbox_id] + converted_bbox = shift_ann( + bbox, + offset_x=roi_shift_x, + offset_y=roi_shift_y, + img_w=roi_info.roi_w, + img_h=roi_info.roi_h, + ) + + skeleton.group = 1 + converted_bbox.group = skeleton.group + + updated_annotations = [skeleton, converted_bbox] + + updated_dataset.put(job_sample.wrap(annotations=updated_annotations)) + + return updated_dataset + def _make_gt_dataset_for_job(self, job_id: int, job_dataset: dm.Dataset) -> dm.Dataset: job_label_cat: dm.LabelCategories = job_dataset.categories()[dm.AnnotationType.label] assert len(job_label_cat) == 2 From 4d8855728eb5bf6b95eb5fa5f970a405822870fd Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Tue, 12 Mar 2024 14:56:13 +0200 Subject: [PATCH 70/82] Finish escrows with too many unverifiable assignments --- .../cvat/recording-oracle/src/.env.template | 10 +++++++- .../cvat/recording-oracle/src/core/config.py | 12 +++++++-- .../src/core/validation_results.py | 3 ++- .../handlers/process_intermediate_results.py | 25 +++++++++++++++++-- .../src/handlers/validation.py | 15 ++++++++--- 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/.env.template b/packages/examples/cvat/recording-oracle/src/.env.template index 285ec69caa..fde9f58ef4 100644 --- a/packages/examples/cvat/recording-oracle/src/.env.template +++ b/packages/examples/cvat/recording-oracle/src/.env.template @@ -62,4 +62,12 @@ LOCALHOST_REPUTATION_ORACLE_URL= # Features -ENABLE_CUSTOM_CLOUD_HOST= \ No newline at end of file +ENABLE_CUSTOM_CLOUD_HOST= + +# Validation + +DEFAULT_POINT_VALIDITY_RELATIVE_RADIUS= +DEFAULT_OKS_SIGMA= +GT_FAILURE_THRESHOLD= +GT_BAN_THRESHOLD= +UNVERIFIABLE_ASSIGNMENTS_THRESHOLD= diff --git a/packages/examples/cvat/recording-oracle/src/core/config.py b/packages/examples/cvat/recording-oracle/src/core/config.py index aa30d33f1b..9d9dde45e3 100644 --- a/packages/examples/cvat/recording-oracle/src/core/config.py +++ b/packages/examples/cvat/recording-oracle/src/core/config.py @@ -144,7 +144,7 @@ class ValidationConfig: gt_failure_threshold = float(os.environ.get("GT_FAILURE_THRESHOLD", 0.9)) """ - The fraction of allowed failed assignments per GT sample, + The maximum allowed fraction of failed assignments per GT sample, before it's considered failed for the current validation iteration. v = 0 -> any GT failure leads to image failure v = 1 -> any GT failures do not lead to image failure @@ -152,7 +152,15 @@ class ValidationConfig: gt_ban_threshold = int(os.environ.get("GT_BAN_THRESHOLD", 3)) """ - The number of allowed failures per GT sample before it's excluded from validation + The maximum allowed number of failures per GT sample before it's excluded from validation + """ + + unverifiable_assignments_threshold = float( + os.environ.get("UNVERIFIABLE_ASSIGNMENTS_THRESHOLD", 0.1) + ) + """ + The maximum allowed fraction of jobs with insufficient GT available for validation. + Each such job will be accepted "blindly", as we can't validate the annotations. """ diff --git a/packages/examples/cvat/recording-oracle/src/core/validation_results.py b/packages/examples/cvat/recording-oracle/src/core/validation_results.py index 3a50ddea5c..8d78bfc7c8 100644 --- a/packages/examples/cvat/recording-oracle/src/core/validation_results.py +++ b/packages/examples/cvat/recording-oracle/src/core/validation_results.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import Dict +from src.core.validation_errors import DatasetValidationError from src.core.validation_meta import ValidationMeta @@ -13,4 +14,4 @@ class ValidationSuccess: @dataclass class ValidationFailure: - rejected_jobs: Dict[int, str] + rejected_jobs: Dict[int, DatasetValidationError] diff --git a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py index 3cea860561..f0eb281817 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/process_intermediate_results.py @@ -947,10 +947,31 @@ def process_intermediate_results( job_final_result_ids[job.id] = assignment_validation_result_id - if rejected_jobs: + task_jobs = task.jobs + + should_complete = False + total_jobs = len(task_jobs) + unverifiable_jobs_count = len( + [v for v in rejected_jobs.values() if isinstance(v, TooFewGtError)] + ) + if total_jobs * Config.validation.unverifiable_assignments_threshold < unverifiable_jobs_count: + logger.info( + "Validation for escrow_address={}: " + "too many assignments have insufficient GT for validation ({} of {} ({:.2f}%)), " + "stopping annotation".format( + escrow_address, + unverifiable_jobs_count, + total_jobs, + unverifiable_jobs_count / total_jobs * 100, + ) + ) + should_complete = True + elif not rejected_jobs: + should_complete = True + + if not should_complete: return ValidationFailure(rejected_jobs) - task_jobs = task.jobs task_validation_results = db_service.get_task_validation_results(session, task.id) job_id_to_meta_id = {job.id: i for i, job in enumerate(task_jobs)} diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index 8f7af77fa0..24e73de7f4 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -20,6 +20,7 @@ compose_results_bucket_filename as compose_annotation_results_bucket_filename, ) from src.core.types import OracleWebhookTypes +from src.core.validation_errors import TooFewGtError from src.core.validation_results import ValidationFailure, ValidationSuccess from src.handlers.process_intermediate_results import ( parse_annotation_metafile, @@ -130,7 +131,7 @@ def _handle_validation_result(self, validation_result: ValidationResult): if isinstance(validation_result, ValidationSuccess): logger.info( - f"Validation for escrow_address={escrow_address} successful, " + f"Validation for escrow_address={escrow_address}: successful, " f"average annotation quality is {validation_result.average_quality:.2f}" ) @@ -181,7 +182,7 @@ def _handle_validation_result(self, validation_result: ValidationResult): type(e).__name__ for e in validation_result.rejected_jobs.values() ) logger.info( - f"Validation for escrow_address={escrow_address} failed, " + f"Validation for escrow_address={escrow_address}: failed, " f"rejected {len(validation_result.rejected_jobs)} jobs. " f"Problems: {dict(error_type_counts)}" ) @@ -192,8 +193,14 @@ def _handle_validation_result(self, validation_result: ValidationResult): chain_id, OracleWebhookTypes.exchange_oracle, event=RecordingOracleEvent_TaskRejected( - # TODO: update wrt. M2 API changes - rejected_job_ids=list(validation_result.rejected_jobs) + # TODO: update wrt. M2 API changes, send reason + rejected_job_ids=list( + jid + for jid, reason in validation_result.rejected_jobs + if not isinstance( + reason, TooFewGtError + ) # prevent such jobs from reannotation, can also be handled in ExcOr + ) ), ) From a1cb001e06007388635b2e31171db13bb02007ed Mon Sep 17 00:00:00 2001 From: Dzeranov Date: Tue, 12 Mar 2024 17:55:12 +0300 Subject: [PATCH 71/82] Check if an increased healthcheck interval will fix unhealthy container (#1700) --- packages/examples/cvat/recording-oracle/docker-compose.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/examples/cvat/recording-oracle/docker-compose.test.yml b/packages/examples/cvat/recording-oracle/docker-compose.test.yml index 553cf7957c..0d009890e6 100644 --- a/packages/examples/cvat/recording-oracle/docker-compose.test.yml +++ b/packages/examples/cvat/recording-oracle/docker-compose.test.yml @@ -22,7 +22,7 @@ services: healthcheck: # Using a magic nubmer of 28 here because this is a block number when blockchain-node container is ready to use test: if [ $(( $(wget -q --post-data='{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' -O- http://blockchain-node:8545 | grep -o '"result":"[^"]*"' | awk -F'"' '{print $4}' ) )) -ge 28 ]; then exit 0; else exit 1; fi - interval: 5s + interval: 15s timeout: 5s retries: 15 networks: From 4445a8451e7855edfac28453863a64e7ec29d97f Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Wed, 20 Mar 2024 18:57:24 +0200 Subject: [PATCH 72/82] Fix validations for boxes from points task creation --- .../src/handlers/job_creation.py | 27 +++++++++---------- .../exchange-oracle/src/utils/annotations.py | 1 + 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 3e63a04de6..f71e7aac09 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -180,7 +180,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.embedded_point_color = (0, 255, 255) self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) - self.min_class_samples_for_roi_estimation = 50 + self.min_class_samples_for_roi_estimation = 25 self.max_discarded_threshold = 0.05 """ @@ -253,7 +253,7 @@ def _validate_gt_labels(self): if gt_labels - manifest_labels: raise DatasetValidationError( "GT labels do not match job labels. Unknown labels: {}".format( - self._format_list(gt_labels - manifest_labels), + self._format_list(list(gt_labels - manifest_labels)), ) ) @@ -296,11 +296,10 @@ def _validate_gt_annotations(self): sample_boxes = [a for a in gt_sample.annotations if isinstance(a, dm.Bbox)] valid_boxes = [] for bbox in sample_boxes: - if (0 <= bbox.x < bbox.x + bbox.w < img_w) and ( - 0 <= bbox.y < bbox.y + bbox.h < img_h + if not ( + (0 <= bbox.x < bbox.x + bbox.w <= img_w) + and (0 <= bbox.y < bbox.y + bbox.h <= img_h) ): - valid_boxes.append(bbox) - else: excluded_gt_info.add_error( "Sample '{}': GT bbox #{} ({}) - invalid coordinates. " "The image will be skipped".format( @@ -633,6 +632,13 @@ def _prepare_gt(self): gt_dataset.put(gt_sample.wrap(annotations=matched_boxes)) + if excluded_gt_info.excluded_count: + self.logger.warning( + "Some GT annotations were excluded due to the errors found: {}".format( + self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") + ) + ) + if ( excluded_gt_info.excluded_count > excluded_gt_info.total_count * self.max_discarded_threshold @@ -645,13 +651,6 @@ def _prepare_gt(self): ) ) - if excluded_gt_info.excluded_count: - self.logger.warning( - "Some GT annotations were excluded due to the errors found: {}".format( - self._format_list([e.message for e in excluded_gt_info.errors], separator="\n") - ) - ) - gt_labels_without_anns = [ gt_label_cat[label_id] for label_id, label_count in gt_count_per_class.items() @@ -827,10 +826,8 @@ def _upload_task_meta(self): ) storage_client = self._make_cloud_storage_client(self.oracle_data_bucket) - bucket_name = self.oracle_data_bucket.bucket_name for file_data, filename in file_list: storage_client.create_file( - bucket_name, compose_data_bucket_filename(self.escrow_address, self.chain_id, filename), file_data, ) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py index 41d1bb6fa8..2dbf33d88c 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -142,6 +142,7 @@ def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str] T = TypeVar("T", bound=dm.Annotation) + def shift_ann(ann: T, offset_x: float, offset_y: float, *, img_w: int, img_h: int) -> T: "Shift annotation coordinates with clipping to the image size" From ecc93af724ba782bc43ae647e4564fd226d16090 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 14:57:53 +0200 Subject: [PATCH 73/82] Clean code --- .../examples/cvat/exchange-oracle/src/handlers/job_creation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index f71e7aac09..44852b2ce2 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -1078,9 +1078,6 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.roi_background_color = (245, 240, 242) # BGR - CVAT background color self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) - # TODO: add - # credentials=BucketCredentials() - "Exchange Oracle's private bucket info" self.min_label_gt_samples = 2 # TODO: find good threshold From cbc625bff8109aaf2089fda10a4cb81c9d458c20 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 14:58:48 +0200 Subject: [PATCH 74/82] disable roi estimation for unreliable cases --- .../src/handlers/job_creation.py | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 44852b2ce2..dc75a2e272 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -178,10 +178,19 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.min_embedded_point_radius_percent = 0.005 self.max_embedded_point_radius_percent = 0.01 self.embedded_point_color = (0, 255, 255) + self.roi_background_color = (245, 240, 242) # BGR - CVAT background color self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) + self.min_class_samples_for_roi_estimation = 25 + self.max_class_roi_image_side_threshold = 0.5 + """ + The maximum allowed percent of the image for the estimated class RoI, + before the default RoI is used. Too big RoI estimations reduce the overall + prediction quality, making them unreliable. + """ + self.max_discarded_threshold = 0.05 """ The maximum allowed percent of discarded @@ -689,25 +698,39 @@ def _estimate_roi_sizes(self): # For big enough datasets, it should be reasonable approximation # (due to the central limit theorem). This can work bad for small datasets, # so we only do this if there are enough class samples. - classes_with_default_roi = [] + classes_with_default_roi: dict[int, str] = {} # label_id -> reason roi_size_estimations_per_label = {} # label id -> (w, h) + default_roi_size = (2, 2) # 2 will yield just the image size after halving for label_id, label_sizes in bbox_sizes_per_label.items(): if len(label_sizes) < self.min_class_samples_for_roi_estimation: - classes_with_default_roi.append(label_id) - estimated_size = (2, 2) # 2 will yield just the image size after halving + estimated_size = default_roi_size + classes_with_default_roi[label_id] = "too few GT provided" else: max_bbox = np.max(label_sizes, axis=0) - estimated_size = max_bbox * self.roi_size_mult + if np.any(max_bbox > self.max_class_roi_image_side_threshold): + estimated_size = default_roi_size + classes_with_default_roi[label_id] = "estimated RoI is unreliable" + else: + estimated_size = 2 * max_bbox * self.roi_size_mult roi_size_estimations_per_label[label_id] = estimated_size if classes_with_default_roi: label_cat = self._gt_dataset.categories()[dm.AnnotationType.label] + labels_by_reason = { + g_reason: list(v[0] for v in g_items) + for g_reason, g_items in groupby( + sorted(classes_with_default_roi.items(), key=lambda v: v[1]), key=lambda v: v[1] + ) + } self.logger.warning( - "Some classes will use the full image instead of RoI" - "- too few GT provided: {}".format( - self._format_list( - [label_cat[label_id].name for label_id in classes_with_default_roi] + "Some classes will use the full image instead of RoI - {}".format( + "; ".join( + "{}: {}".format( + g_reason, + self._format_list([label_cat[label_id].name for label_id in g_labels]), + ) + for g_reason, g_labels in labels_by_reason.items() ) ) ) @@ -832,6 +855,38 @@ def _upload_task_meta(self): file_data, ) + def _extract_roi( + self, source_pixels: np.ndarray, roi_info: boxes_from_points_task.RoiInfo + ) -> np.ndarray: + img_h, img_w, *_ = source_pixels.shape + + roi_pixels = source_pixels[ + max(0, roi_info.roi_y) : min(img_h, roi_info.roi_y + roi_info.roi_h), + max(0, roi_info.roi_x) : min(img_w, roi_info.roi_x + roi_info.roi_w), + ] + + if not ( + (0 <= roi_info.roi_x < roi_info.roi_x + roi_info.roi_w < img_w) + and (0 <= roi_info.roi_y < roi_info.roi_y + roi_info.roi_h < img_h) + ): + # Coords can be outside the original image + # In this case a border should be added to RoI, so that the image was centered on bbox + wrapped_roi_pixels = np.zeros((roi_info.roi_h, roi_info.roi_w, 3), dtype=np.float32) + wrapped_roi_pixels[:, :] = self.roi_background_color + + dst_y = max(-roi_info.roi_y, 0) + dst_x = max(-roi_info.roi_x, 0) + wrapped_roi_pixels[ + dst_y : dst_y + roi_pixels.shape[0], + dst_x : dst_x + roi_pixels.shape[1], + ] = roi_pixels + + roi_pixels = wrapped_roi_pixels + else: + roi_pixels = roi_pixels.copy() + + return roi_pixels + def _draw_roi_point( self, roi_pixels: np.ndarray, roi_info: boxes_from_points_task.RoiInfo ) -> np.ndarray: @@ -865,6 +920,7 @@ def _draw_roi_point( def _extract_and_upload_rois(self): # TODO: maybe optimize via splitting into separate threads (downloading, uploading, processing) + # Watch for the memory used, as the whole dataset can be quite big (gigabytes, terabytes) # Consider also packing RoIs cut into archives assert self._points_dataset is not _unset @@ -910,10 +966,7 @@ def _extract_and_upload_rois(self): image_rois = {} for roi_info in image_roi_infos: - roi_pixels = image_pixels[ - roi_info.roi_y : roi_info.roi_y + roi_info.roi_h, - roi_info.roi_x : roi_info.roi_x + roi_info.roi_w, - ] + roi_pixels = self._extract_roi(image_pixels, roi_info) if self.embed_point_in_roi_image: roi_pixels = self._draw_roi_point(roi_pixels, roi_info) From 71cbf1aadbf8d8021107d242d4fd2a6756918452 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 21:24:47 +0200 Subject: [PATCH 75/82] Fix manifest parsing --- .../cvat/recording-oracle/src/handlers/validation.py | 5 +++-- .../examples/cvat/recording-oracle/src/utils/assignments.py | 6 ------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/handlers/validation.py b/packages/examples/cvat/recording-oracle/src/handlers/validation.py index 24e73de7f4..b46c3ff13e 100644 --- a/packages/examples/cvat/recording-oracle/src/handlers/validation.py +++ b/packages/examples/cvat/recording-oracle/src/handlers/validation.py @@ -30,7 +30,8 @@ from src.log import ROOT_LOGGER_NAME from src.services.cloud import make_client as make_cloud_client from src.services.cloud.utils import BucketAccessInfo -from src.utils.assignments import compute_resulting_annotations_hash, parse_manifest +from src.core.manifest import parse_manifest +from src.utils.assignments import compute_resulting_annotations_hash from src.utils.logging import NullLogger, get_function_logger module_logger_name = f"{ROOT_LOGGER_NAME}.cron.webhook" @@ -196,7 +197,7 @@ def _handle_validation_result(self, validation_result: ValidationResult): # TODO: update wrt. M2 API changes, send reason rejected_job_ids=list( jid - for jid, reason in validation_result.rejected_jobs + for jid, reason in validation_result.rejected_jobs.items() if not isinstance( reason, TooFewGtError ) # prevent such jobs from reannotation, can also be handled in ExcOr diff --git a/packages/examples/cvat/recording-oracle/src/utils/assignments.py b/packages/examples/cvat/recording-oracle/src/utils/assignments.py index dc3da37e50..1453425d8d 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/recording-oracle/src/utils/assignments.py @@ -1,11 +1,5 @@ from hashlib import sha256 -from src.core.manifest import TaskManifest - - -def parse_manifest(manifest: dict) -> TaskManifest: - return TaskManifest.parse_obj(manifest) - def compute_resulting_annotations_hash(data: bytes) -> str: return sha256(data, usedforsecurity=False).hexdigest() From 8c9e6ac7827a4e18035c1fed6479da2e7e3708c5 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 21:31:16 +0200 Subject: [PATCH 76/82] Remove m0 launcher stubs --- .../src/crons/process_job_launcher_webhooks.py | 7 ------- .../examples/cvat/exchange-oracle/src/utils/annotations.py | 1 + .../examples/cvat/exchange-oracle/src/utils/webhooks.py | 3 +++ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py index 7707251359..a3f527fa0b 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/process_job_launcher_webhooks.py @@ -188,13 +188,6 @@ def process_outgoing_job_launcher_webhooks(): timestamp=None, # TODO: launcher doesn't support it yet ) - # TODO: remove when field naming is updated in launcher - body["escrowAddress"] = body.pop("escrow_address") - body["chainId"] = body.pop("chain_id") - body["eventType"] = body.pop("event_type") - body["eventData"] = body.pop("event_data") - # ^^^ - _, signature = prepare_signed_message( webhook.escrow_address, webhook.chain_id, diff --git a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py index 41d1bb6fa8..2dbf33d88c 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/annotations.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/annotations.py @@ -142,6 +142,7 @@ def remove_duplicated_gt_frames(dataset: dm.Dataset, known_frames: Sequence[str] T = TypeVar("T", bound=dm.Annotation) + def shift_ann(ann: T, offset_x: float, offset_y: float, *, img_w: int, img_h: int) -> T: "Shift annotation coordinates with clipping to the image size" diff --git a/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py b/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py index 31678b9af1..acb534c265 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/webhooks.py @@ -20,7 +20,10 @@ def prepare_outgoing_webhook_body( event = parse_event(OracleWebhookTypes.exchange_oracle, event_type, event_data) body["event_type"] = event_type + body["event_data"] = event.dict() + if not body["event_data"]: + body.pop("event_data") return body From 80ddca45b8cb35c9eae6b918b51333a18dd1a2a1 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 21:33:23 +0200 Subject: [PATCH 77/82] Remove m0 rep or stubs --- .../src/crons/process_reputation_oracle_webhooks.py | 10 ---------- .../cvat/recording-oracle/src/utils/webhooks.py | 3 +++ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py index a9eb2d37a9..863adbef9a 100644 --- a/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/crons/process_reputation_oracle_webhooks.py @@ -48,16 +48,6 @@ def process_outgoing_reputation_oracle_webhooks(): timestamp=None, # TODO: reputation oracle doesn't support ) - # TODO: remove compatibility code - # FIXME: For a sake of compatibility with the current - # version of Reputation Oracle keep this - # vvv - body["escrowAddress"] = body.pop("escrow_address") - body["chainId"] = body.pop("chain_id") - body["eventType"] = body.pop("event_type") - body.pop("event_data") - # ^^^ - _, signature = prepare_signed_message( webhook.escrow_address, webhook.chain_id, diff --git a/packages/examples/cvat/recording-oracle/src/utils/webhooks.py b/packages/examples/cvat/recording-oracle/src/utils/webhooks.py index bef1bcc459..e6d39202a3 100644 --- a/packages/examples/cvat/recording-oracle/src/utils/webhooks.py +++ b/packages/examples/cvat/recording-oracle/src/utils/webhooks.py @@ -20,7 +20,10 @@ def prepare_outgoing_webhook_body( event = parse_event(OracleWebhookTypes.recording_oracle, event_type, event_data) body["event_type"] = event_type + body["event_data"] = event.dict() + if not body["event_data"]: + body.pop("event_data") return body From ae176553a526b2791d601bb12d4cb576583f3faf Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 21:52:54 +0200 Subject: [PATCH 78/82] Make max assignment time optional in manifest --- .../cvat/exchange-oracle/src/core/config.py | 2 +- .../cvat/exchange-oracle/src/core/manifest.py | 3 +-- .../src/core/tasks/skeletons_from_boxes.py | 2 ++ .../exchange-oracle/src/handlers/job_creation.py | 5 +---- .../cvat/exchange-oracle/src/services/exchange.py | 15 ++++++++++++--- .../cvat/exchange-oracle/src/utils/assignments.py | 10 ++++++++++ .../cvat/recording-oracle/src/core/manifest.py | 2 +- 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/config.py b/packages/examples/cvat/exchange-oracle/src/core/config.py index 6e04099fe7..00c9226fb4 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/config.py +++ b/packages/examples/cvat/exchange-oracle/src/core/config.py @@ -148,7 +148,7 @@ class FeaturesConfig: class CoreConfig: - default_assignment_time = int(os.environ.get("DEFAULT_ASSIGNMENT_TIME", 300)) + default_assignment_time = int(os.environ.get("DEFAULT_ASSIGNMENT_TIME", 1800)) class HumanAppConfig: diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index a3dbef6499..6d9d91faae 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -4,7 +4,6 @@ from pydantic import AnyUrl, BaseModel, Field, root_validator -from src.core.config import Config from src.core.types import TaskTypes from src.utils.enums import BetterEnumMeta @@ -133,7 +132,7 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: int = Field(default_factory=lambda: Config.core_config.default_assignment_time) + max_time: Optional[int] = None # deprecated, TODO: mark deprecated with pydantic 2.7+ "Maximum time per job (assignment) for an annotator, in seconds" diff --git a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py index 99eaacb285..d0d1832482 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py +++ b/packages/examples/cvat/exchange-oracle/src/core/tasks/skeletons_from_boxes.py @@ -8,6 +8,8 @@ from attrs import frozen from datumaro.util import dump_json, parse_json +DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER = 2 * 3 # tile grid size + SkeletonBboxMapping = Dict[int, int] diff --git a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py index 3e63a04de6..de016df391 100644 --- a/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py +++ b/packages/examples/cvat/exchange-oracle/src/handlers/job_creation.py @@ -1057,7 +1057,7 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self._excluded_boxes_info: _MaybeUnset[_ExcludedAnnotationsInfo] = _unset # Configuration / constants - self.job_size_mult = 6 + self.job_size_mult = skeletons_from_boxes_task.DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER "Job size multiplier" # TODO: consider WebP if produced files are too big @@ -1081,9 +1081,6 @@ def __init__(self, manifest: TaskManifest, escrow_address: str, chain_id: int): self.roi_background_color = (245, 240, 242) # BGR - CVAT background color self.oracle_data_bucket = BucketAccessInfo.parse_obj(Config.storage_config) - # TODO: add - # credentials=BucketCredentials() - "Exchange Oracle's private bucket info" self.min_label_gt_samples = 2 # TODO: find good threshold diff --git a/packages/examples/cvat/exchange-oracle/src/services/exchange.py b/packages/examples/cvat/exchange-oracle/src/services/exchange.py index b2989bf0ab..20f17c0423 100644 --- a/packages/examples/cvat/exchange-oracle/src/services/exchange.py +++ b/packages/examples/cvat/exchange-oracle/src/services/exchange.py @@ -8,7 +8,11 @@ from src.core.types import AssignmentStatuses, JobStatuses, PlatformTypes, ProjectStatuses from src.db import SessionLocal from src.schemas import exchange as service_api -from src.utils.assignments import compose_assignment_url, parse_manifest +from src.utils.assignments import ( + compose_assignment_url, + get_default_assignment_timeout, + parse_manifest, +) from src.utils.requests import get_or_404 from src.utils.time import utcnow @@ -43,7 +47,8 @@ def serialize_task( title=f"Task {project.escrow_address[:10]}", description=manifest.annotation.description, job_bounty=manifest.job_bounty, - job_time_limit=manifest.annotation.max_time, + job_time_limit=manifest.annotation.max_time + or get_default_assignment_timeout(manifest.annotation.type), job_size=manifest.annotation.job_size + manifest.validation.val_size, job_type=project.job_type, platform=PlatformTypes.CVAT, @@ -159,7 +164,11 @@ def create_assignment(project_id: int, wallet_address: str) -> Optional[str]: session, wallet_address=user.wallet_address, cvat_job_id=unassigned_job.cvat_id, - expires_at=now + timedelta(seconds=manifest.annotation.max_time), + expires_at=now + + timedelta( + seconds=manifest.annotation.max_time + or get_default_assignment_timeout(manifest.annotation.type) + ), ) cvat_api.clear_job_annotations(unassigned_job.cvat_id) diff --git a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py index 3037238da8..cbfa9b4a71 100644 --- a/packages/examples/cvat/exchange-oracle/src/utils/assignments.py +++ b/packages/examples/cvat/exchange-oracle/src/utils/assignments.py @@ -3,6 +3,7 @@ from src.core.config import Config from src.core.manifest import TaskManifest, TaskTypes from src.core.manifest import parse_manifest as _parse_manifest +from src.core.tasks import skeletons_from_boxes from src.models.cvat import Project @@ -19,3 +20,12 @@ def compose_assignment_url(task_id: int, job_id: int, *, project: Project) -> st query_params = "?defaultWorkspace=single_shape" return urljoin(Config.cvat_config.cvat_url, f"/tasks/{task_id}/jobs/{job_id}{query_params}") + + +def get_default_assignment_timeout(task_type: TaskTypes) -> int: + timeout_seconds = Config.core_config.default_assignment_time + + if task_type == TaskTypes.image_skeletons_from_boxes: + timeout_seconds *= skeletons_from_boxes.DEFAULT_ASSIGNMENT_SIZE_MULTIPLIER + + return timeout_seconds diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index c20ab628ae..5b2190bb15 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -133,7 +133,7 @@ class AnnotationInfo(BaseModel): job_size: int = 10 "Frames per job, validation frames are not included" - max_time: Optional[int] = None + max_time: Optional[int] = None # deprecated, TODO: mark deprecated with pydantic 2.7+ "Maximum time per job (assignment) for an annotator, in seconds" From 4f5b68e1c9f192fa7391a0dbb0746641bf383372 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Fri, 22 Mar 2024 22:25:51 +0200 Subject: [PATCH 79/82] Make label type fully optional in manifest --- .../cvat/exchange-oracle/src/core/manifest.py | 29 +++++++++++------- .../recording-oracle/src/core/manifest.py | 30 +++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/core/manifest.py b/packages/examples/cvat/exchange-oracle/src/core/manifest.py index 6d9d91faae..24cfd750b2 100644 --- a/packages/examples/cvat/exchange-oracle/src/core/manifest.py +++ b/packages/examples/cvat/exchange-oracle/src/core/manifest.py @@ -135,6 +135,24 @@ class AnnotationInfo(BaseModel): max_time: Optional[int] = None # deprecated, TODO: mark deprecated with pydantic 2.7+ "Maximum time per job (assignment) for an annotator, in seconds" + @root_validator(pre=True) + @classmethod + def _validate_label_type(cls, values: dict[str, Any]) -> dict[str, Any]: + default_label_type = LabelTypes.plain + if values["type"] == TaskTypes.image_skeletons_from_boxes: + default_label_type = LabelTypes.skeleton + + # Add default value for labels, if none provided. + # pydantic can't do this for tagged unions + try: + labels = values["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", default_label_type) + except KeyError: + pass + + return values + class ValidationInfo(BaseModel): min_quality: float = Field(ge=0) @@ -157,15 +175,4 @@ class TaskManifest(BaseModel): def parse_manifest(manifest: Any) -> TaskManifest: - # Add default value for labels, if none provided. - # pydantic can't do this for tagged unions - - if isinstance(manifest, dict): - try: - labels = manifest["annotation"]["labels"] - for label_info in labels: - label_info["type"] = label_info.get("type", LabelTypes.plain) - except KeyError: - pass - return TaskManifest.parse_obj(manifest) diff --git a/packages/examples/cvat/recording-oracle/src/core/manifest.py b/packages/examples/cvat/recording-oracle/src/core/manifest.py index 5b2190bb15..24cfd750b2 100644 --- a/packages/examples/cvat/recording-oracle/src/core/manifest.py +++ b/packages/examples/cvat/recording-oracle/src/core/manifest.py @@ -4,7 +4,6 @@ from pydantic import AnyUrl, BaseModel, Field, root_validator -from src.core.config import Config from src.core.types import TaskTypes from src.utils.enums import BetterEnumMeta @@ -136,6 +135,24 @@ class AnnotationInfo(BaseModel): max_time: Optional[int] = None # deprecated, TODO: mark deprecated with pydantic 2.7+ "Maximum time per job (assignment) for an annotator, in seconds" + @root_validator(pre=True) + @classmethod + def _validate_label_type(cls, values: dict[str, Any]) -> dict[str, Any]: + default_label_type = LabelTypes.plain + if values["type"] == TaskTypes.image_skeletons_from_boxes: + default_label_type = LabelTypes.skeleton + + # Add default value for labels, if none provided. + # pydantic can't do this for tagged unions + try: + labels = values["labels"] + for label_info in labels: + label_info["type"] = label_info.get("type", default_label_type) + except KeyError: + pass + + return values + class ValidationInfo(BaseModel): min_quality: float = Field(ge=0) @@ -158,15 +175,4 @@ class TaskManifest(BaseModel): def parse_manifest(manifest: Any) -> TaskManifest: - # Add default value for labels, if none provided. - # pydantic can't do this for tagged unions - - if isinstance(manifest, dict): - try: - labels = manifest["annotation"]["labels"] - for label_info in labels: - label_info["type"] = label_info.get("type", LabelTypes.plain) - except KeyError: - pass - return TaskManifest.parse_obj(manifest) From 96463cf08e4f1016e983f795f439c7e54c340e9f Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 25 Mar 2024 16:39:18 +0200 Subject: [PATCH 80/82] Fix test --- .../integration/cron/test_process_job_launcher_webhooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py index bb0d573817..848965aa01 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/test_process_job_launcher_webhooks.py @@ -172,7 +172,7 @@ def test_process_incoming_job_launcher_webhooks_escrow_created_type_exceed_max_r ) self.assertEqual(new_webhook.status, OracleWebhookStatuses.pending.value) - self.assertEqual(new_webhook.event_type, ExchangeOracleEventType.task_creation_failed) + self.assertEqual(new_webhook.event_type, ExchangeOracleEventTypes.task_creation_failed) self.assertEqual(new_webhook.attempts, 0) def test_process_incoming_job_launcher_webhooks_escrow_created_type_remove_when_error( From 3fb0af3b1ea59037d83d23f25074e3e5cb8ce61b Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Mon, 25 Mar 2024 16:39:58 +0200 Subject: [PATCH 81/82] Enable and fix a disabled m1 task creation test --- .../src/crons/state_trackers.py | 3 +- .../test_track_task_creation.py | 59 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py index 069248c7ca..f55a36f7a3 100644 --- a/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py +++ b/packages/examples/cvat/exchange-oracle/src/crons/state_trackers.py @@ -213,11 +213,10 @@ def track_task_creation() -> None: failed: List[cvat_models.DataUpload] = [] for upload in uploads: status, reason = cvat_api.get_task_upload_status(upload.task_id) + project = upload.task.project if not status or status == cvat_api.UploadStatus.FAILED: failed.append(upload) - project = upload.task.project - oracle_db_service.outbox.create_webhook( session, escrow_address=project.escrow_address, diff --git a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py index 5d24c68717..a140355d7d 100644 --- a/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py +++ b/packages/examples/cvat/exchange-oracle/tests/integration/cron/state_trackers/test_track_task_creation.py @@ -81,34 +81,31 @@ def test_track_track_completed_task_creation(self): data_upload = self.session.query(DataUpload).filter_by(id=upload_id).first() self.assertIsNone(data_upload) - # TODO: - # Fix "local variable 'project' referenced before assignment" error in src/crons/state_trackers.py and uncomment this test case - - # def test_track_track_completed_task_creation_error(self): - # escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" - # (_, cvat_task, cvat_job) = create_project_task_and_job(self.session, escrow_address, 1) - # upload = DataUpload( - # id=str(uuid.uuid4()), - # task_id=cvat_task.cvat_id, - # ) - # self.session.add(upload) - # self.session.commit() - - # with ( - # patch( - # "src.crons.state_trackers.cvat_api.get_task_upload_status" - # ) as mock_get_task_upload_status, - # patch( - # "src.crons.state_trackers.cvat_api.fetch_task_jobs", - # side_effect=cvat_api.exceptions.ApiException("Error"), - # ), - # ): - # mock_get_task_upload_status.return_value = (cvat_api.UploadStatus.FINISHED, None) - - # track_task_creation() - - # self.session.commit() - - # webhook = self.session.query(Webhook).filter_by(escrow_address=escrow_address).first() - # self.assertIsNotNone(webhook) - # self.assertEqual(webhook.event_type, ExchangeOracleEventType.task_creation_failed) + def test_track_track_completed_task_creation_error(self): + escrow_address = "0x86e83d346041E8806e352681f3F14549C0d2BC67" + (_, cvat_task, cvat_job) = create_project_task_and_job(self.session, escrow_address, 1) + upload = DataUpload( + id=str(uuid.uuid4()), + task_id=cvat_task.cvat_id, + ) + self.session.add(upload) + self.session.commit() + + with ( + patch( + "src.crons.state_trackers.cvat_api.get_task_upload_status" + ) as mock_get_task_upload_status, + patch( + "src.crons.state_trackers.cvat_api.fetch_task_jobs", + side_effect=cvat_api.exceptions.ApiException("Error"), + ), + ): + mock_get_task_upload_status.return_value = (cvat_api.UploadStatus.FINISHED, None) + + track_task_creation() + + self.session.commit() + + webhook = self.session.query(Webhook).filter_by(escrow_address=escrow_address).first() + self.assertIsNotNone(webhook) + self.assertEqual(webhook.event_type, ExchangeOracleEventTypes.task_creation_failed) From b6450d9a5694d256274bc5867221131cfebd7ff4 Mon Sep 17 00:00:00 2001 From: Maxim Zhiltsov Date: Thu, 28 Mar 2024 12:57:41 +0200 Subject: [PATCH 82/82] Fix RecOr tests --- .../cron/test_process_reputation_oracle_webhooks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py index 4bb2a38ee4..a31c512f85 100644 --- a/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py +++ b/packages/examples/cvat/recording-oracle/tests/integration/cron/test_process_reputation_oracle_webhooks.py @@ -89,9 +89,9 @@ def test_process_reputation_oracle_webhooks(self): expected_url, headers={"human-signature": SIGNATURE}, json={ - "escrowAddress": escrow_address, - "chainId": chain_id, - "eventType": RecordingOracleEventTypes.task_completed.value, + "escrow_address": escrow_address, + "chain_id": chain_id, + "event_type": RecordingOracleEventTypes.task_completed.value, }, ) self.assertEqual(updated_webhook.status, OracleWebhookStatuses.completed.value)