From 3de09517bdf164cc05c3d586f710db3e32090afc Mon Sep 17 00:00:00 2001 From: Nina Date: Mon, 20 Apr 2026 16:08:31 -0500 Subject: [PATCH 1/6] feat(BDC-888): DRS 1.5 New endpoint: OPTIONS /objects/{object_id} - returns a list of Authorizations that can be used to determine how to authorize requests to get object or post object. New endpoint: OPTIONS /objects - Returns authorization information for multiple DrsObjects at once. New endpoint: POST /objects - Get info about multiple DrsObjects New endpoint: POST /objects/access (Bulk Access URLs) - Returns an array of URL objects that can be used to download of multiple DrsObjects. Update to DrsObject's access_methods: region, cloud, available and authorization are added to fields. CLOUD_PROVIDER_MAP and DRS_AUTHORIZATION_METADATA environment variables should be provided to support the cloud field for access_methods and the authorizations field for access_methods. See docs for more information. port changes for single option port bulk changes add bulk abstract pytest fixes test test2 test whether open PR re-runs with a change Work in progess: PR feedback updates add more bulk auth option tests fix flaky tests update docstring test commit revert change code changes for cloud field fix broken tests add test for cloud field Initial changes for region field remove debugging print statement from test more changes for region field and available field fix for unit tests fix test_update_urls_metadata more test fixes switch to string 'true' more test fixes fix test_cloud_field swap fullmatch for search remove regexp vulnerability and add test for region field Fix new unit tests use get_doc instead of manual creation remove extra imports from bottom try this fix clear cache too local unit test fixes Update settings files to try pulling in environment variables first drs 1.5 service info support Run drs 1.5 integration tests Fix access_method overrides Rename access_method test More available fixes drs 1.5 helm branch for integration_tests Add cloud provider domain linking Add get_drs_objects endpoint WIP Work in progress - saving updates WIP - saving updates Work in progress - refactored single resolver/tests Clean up bulk tests/helpers pytest fix Updates for PR review ( passport handling + POST) auth nesting updated within access_methods add single post, make names more consistent improve readability Fix logging Fix merge issues and update test_drs authorization tests Fix Exceptions in DRS to use IndexdUnexpectedError Safely access default_passport_issuer Add default issuers to authz, fix cloud drs unit tests Fix supported_types for open access Debugging logs Move logger init to after blueprint registration Fix defaults for authz, removing debug logs Fix test_single_path_not_found test to use defaults Co-authored-by: jacob50231 Co-authored-by: nrandorf Co-authored-by: SpencerAxelrod --- .github/workflows/ci.yaml | 4 +- .github/workflows/integration_tests.yaml | 2 + .pre-commit-config.yaml | 2 +- .secrets.baseline | 8 +- bin/indexd_settings.py | 21 + deployment/Secrets/indexd_settings.py | 18 + indexclient/__init__.py | 0 indexd/app.py | 17 +- indexd/default_settings.py | 32 +- indexd/drs/blueprint.py | 456 +++++++++++-- indexd/index/blueprint.py | 26 + indexd/index/driver.py | 7 + indexd/urls/blueprint.py | 1 - indexd/utils.py | 56 +- openapis/swagger.yaml | 88 +++ poetry.lock | 773 ++++++++++++----------- pyproject.toml | 1 + tests/default_test_settings.py | 41 +- tests/indexclient/test_client.py | 7 +- tests/test_client.py | 56 +- tests/test_drs.py | 605 ++++++++++++++++-- 21 files changed, 1728 insertions(+), 493 deletions(-) create mode 100644 indexclient/__init__.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5a74f4a02..59d99ca59 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,8 +1,10 @@ name: CI on: push: + branches: + - master pull_request: - types: [opened, reopened] + types: [opened, reopened, synchronize] concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: true diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index fac71ed15..4939bb566 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -8,6 +8,8 @@ jobs: uses: uc-cdis/.github/.github/workflows/integration_tests.yaml@master with: SERVICE_TO_TEST: indexd + TEST_REPO_BRANCH: feat/DRS_15 + HELM_BRANCH: feat/drs_1-5-configs secrets: CI_TEST_ORCID_USERID: ${{ secrets.CI_TEST_ORCID_USERID }} CI_TEST_ORCID_PASSWORD: ${{ secrets.CI_TEST_ORCID_PASSWORD }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff9a90de7..9fc6ba70f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,6 @@ repos: - id: no-commit-to-branch args: [--branch, develop, --branch, master, --pattern, release/.*] - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 25.1.0 hooks: - id: black diff --git a/.secrets.baseline b/.secrets.baseline index ee3239b4d..560bf1946 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -118,7 +118,7 @@ "filename": ".github/workflows/ci.yaml", "hashed_secret": "3e26d6750975d678acb8fa35a0f69237881576b0", "is_verified": false, - "line_number": 15 + "line_number": 17 } ], "README.md": [ @@ -296,7 +296,7 @@ "filename": "tests/default_test_settings.py", "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_verified": false, - "line_number": 39 + "line_number": 69 } ], "tests/indexclient/test_client.py": [ @@ -482,9 +482,9 @@ "filename": "tests/test_drs.py", "hashed_secret": "5666c088b494f26cd8f63ace013992f5fc391ce0", "is_verified": false, - "line_number": 39 + "line_number": 45 } ] }, - "generated_at": "2026-04-02T06:52:45Z" + "generated_at": "2026-06-09T13:28:27Z" } diff --git a/bin/indexd_settings.py b/bin/indexd_settings.py index 2f3dd6d3a..a8626f8b0 100644 --- a/bin/indexd_settings.py +++ b/bin/indexd_settings.py @@ -35,6 +35,10 @@ def load_json(file_name): if drs_service_info: CONFIG["DRS_SERVICE_INFO"] = json.loads(drs_service_info) +max_bulk = environ.get("MAX_BULK_REQUEST_LENGTH", None) +if max_bulk: + CONFIG["MAX_BULK_REQUEST_LENGTH"] = int(max_bulk) + if USE_SINGLE_TABLE is True: CONFIG["INDEX"] = { "driver": SingleTableSQLAlchemyIndexDriver( @@ -86,4 +90,21 @@ def load_json(file_name): arborist="http://localhost/", ) +cloud_provider_map = environ.get("CLOUD_PROVIDER_MAP", None) +if cloud_provider_map: + CONFIG["CLOUD_PROVIDER_MAP"] = json.loads(cloud_provider_map) +else: + CONFIG["CLOUD_PROVIDER_MAP"] = { + "s3": "aws", + "gs": "gcp", + "az": "azure", + } settings = {"config": CONFIG, "auth": AUTH, "use_single_table": USE_SINGLE_TABLE} + +drs_authorization_metadata = environ.get("DRS_AUTHORIZATION_METADATA", None) +if drs_authorization_metadata: + CONFIG["DRS_AUTHORIZATION_METADATA"] = json.loads(drs_authorization_metadata) + +default_bearer_issuer = environ.get("DEFAULT_BEARER_ISSUER", None) +if default_bearer_issuer: + CONFIG["DEFAULT_BEARER_ISSUER"] = json.loads(default_bearer_issuer) diff --git a/deployment/Secrets/indexd_settings.py b/deployment/Secrets/indexd_settings.py index 5baab4160..2e39c58de 100644 --- a/deployment/Secrets/indexd_settings.py +++ b/deployment/Secrets/indexd_settings.py @@ -35,6 +35,10 @@ def load_json(file_name): if drs_service_info: CONFIG["DRS_SERVICE_INFO"] = json.loads(drs_service_info) +max_bulk = environ.get("MAX_BULK_REQUEST_LENGTH", None) +if max_bulk: + CONFIG["MAX_BULK_REQUEST_LENGTH"] = int(max_bulk) + if USE_SINGLE_TABLE is True: CONFIG["INDEX"] = { "driver": SingleTableSQLAlchemyIndexDriver( @@ -86,4 +90,18 @@ def load_json(file_name): arborist="http://arborist-service/", ) +cloud_provider_map = environ.get("CLOUD_PROVIDER_MAP", None) +if cloud_provider_map: + CONFIG["CLOUD_PROVIDER_MAP"] = json.loads(cloud_provider_map) +else: + CONFIG["CLOUD_PROVIDER_MAP"] = { + "s3": "aws", + "gs": "gcp", + "az": "azure", + } + settings = {"config": CONFIG, "auth": AUTH, "use_single_table": USE_SINGLE_TABLE} + +drs_authorization_metadata = environ.get("DRS_AUTHORIZATION_METADATA", None) +if drs_authorization_metadata: + CONFIG["DRS_AUTHORIZATION_METADATA"] = json.loads(drs_authorization_metadata) diff --git a/indexclient/__init__.py b/indexclient/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexd/app.py b/indexd/app.py index a2e064557..a41d239c1 100644 --- a/indexd/app.py +++ b/indexd/app.py @@ -18,6 +18,7 @@ from .guid.blueprint import blueprint as indexd_drs_blueprint from .blueprint import blueprint as cross_blueprint from indexd.urls.blueprint import blueprint as index_urls_blueprint +from cachelib import SimpleCache logger = cdislogging.get_logger("indexd", log_level="debug") @@ -49,14 +50,6 @@ def app_init(app, settings=None): else: logger.info("Auto migrations are disabled") - # Alembic may disable existing loggers. Re-apply cdislogging config after migrations. - cdislogging.get_logger( - "indexd", - log_level="debug" if app.config.get("DEBUG") is True else "info", - ).disabled = False - enable_indexd_loggers() - logger.info("indexd logging initialized") - validate_config(settings) app.auth = settings["auth"] @@ -69,6 +62,14 @@ def app_init(app, settings=None): app.register_blueprint(indexd_guid_blueprint) app.register_blueprint(cross_blueprint) app.register_blueprint(index_urls_blueprint, url_prefix="/_query/urls") + app.cache = SimpleCache(default_timeout=1800) + # Alembic may disable existing loggers. Re-apply cdislogging config after migrations. + cdislogging.get_logger( + "indexd", + log_level="debug" if app.config.get("DEBUG") is True else "info", + ).disabled = False + enable_indexd_loggers() + logger.info("indexd logging initialized") def enable_indexd_loggers(): diff --git a/indexd/default_settings.py b/indexd/default_settings.py index 9ac5a9800..b312de0d5 100644 --- a/indexd/default_settings.py +++ b/indexd/default_settings.py @@ -2,6 +2,9 @@ from .alias.drivers.alchemy import SQLAlchemyAliasDriver from .auth.drivers.alchemy import SQLAlchemyAuthDriver from .index.drivers.single_table_alchemy import SingleTableSQLAlchemyIndexDriver +import json +from os import environ +import json CONFIG = {} @@ -68,14 +71,18 @@ }, ] +# Maximum number of objects in a single bulk DRS request. +# Used in GET /service-info response and enforced by bulk endpoints. +CONFIG["MAX_BULK_REQUEST_LENGTH"] = 100 + CONFIG["DRS_SERVICE_INFO"] = { "name": "DRS System", "type": { "group": "org.ga4gh", "artifact": "drs", - "version": "1.0.3", + "version": "1.5.0", }, - "version": "1.0.3", + "version": "1.5.0", "id": "com.example", "organization": { "name": "CTDS", @@ -83,8 +90,29 @@ }, } +cloud_provider_map = environ.get("CLOUD_PROVIDER_MAP", None) +if cloud_provider_map: + CONFIG["CLOUD_PROVIDER_MAP"] = json.loads(cloud_provider_map) +else: + CONFIG["CLOUD_PROVIDER_MAP"] = { + "s3": "aws", + "gs": "gcp", + "az": "azure", + } + AUTH = SQLAlchemyAuthDriver( "postgresql://postgres:postgres@localhost:5432/indexd_tests" # pragma: allowlist secret ) settings = {"config": CONFIG, "auth": AUTH, "use_single_table": USE_SINGLE_TABLE} + +CONFIG["DRS_AUTHORIZATION_METADATA"] = { + "/gen3/programs/a/projects/b": { + "supported_types": ["BearerAuth", "PassportAuth"], + "passport_auth_issuers": ["https://ras/foo/bar"], + "bearer_auth_issuers": ["https://gen3.datacommons.io"], + } +} + +CONFIG["DEFAULT_BEARER_ISSUER"] = "https://gen3.datacommons.io" +CONFIG["DEFAULT_PASSPORT_ISSUER"] = "test_passport_default" diff --git a/indexd/drs/blueprint.py b/indexd/drs/blueprint.py index 0ffcbc097..49081163e 100644 --- a/indexd/drs/blueprint.py +++ b/indexd/drs/blueprint.py @@ -1,24 +1,31 @@ import os -import re +from urllib.parse import urlparse + import flask import json +from cdislogging import get_logger +import copy from indexd.errors import AuthError, AuthzError from indexd.errors import UserError from indexd.index.errors import NoRecordFound as IndexNoRecordFound from indexd.errors import IndexdUnexpectedError -from indexd.utils import reverse_url +from indexd.utils import reverse_url, lookup_bucket_region, get_bucket_regions blueprint = flask.Blueprint("drs", __name__) blueprint.config = dict() blueprint.index_driver = None blueprint.service_info = {} +blueprint.cloud_provider_map = {} +blueprint.max_bulk_request_length = 100 +blueprint.drs_authorization_metadata = {} +logger = get_logger(__name__) @blueprint.route("/ga4gh/drs/v1/service-info", methods=["GET"]) def get_drs_service_info(): """ - Returns DRS compliant service information + Returns DRS 1.5 compliant service information """ reverse_domain_name = reverse_url(url=os.environ["HOSTNAME"]) @@ -26,11 +33,11 @@ def get_drs_service_info(): ret = { "id": reverse_domain_name, "name": "DRS System", - "version": "1.0.3", + "version": "1.5.0", "type": { "group": "org.ga4gh", "artifact": "drs", - "version": "1.0.3", + "version": "1.5.0", }, "organization": { "name": "CTDS", @@ -38,6 +45,7 @@ def get_drs_service_info(): }, } + # Merge config overrides (e.g. from DRS_SERVICE_INFO env var) if blueprint.service_info: for key, value in blueprint.service_info.items(): if key in ret: @@ -47,10 +55,38 @@ def get_drs_service_info(): else: ret[key] = value + # Fetch stats from stats table + object_count = None + total_object_size = None + try: + object_count, total_object_size = blueprint.index_driver.get_stats() + except Exception as e: + logger.warning(f"Could not retrieve stats for service-info response: {e}") + + # Build drs sub-object + max_bulk = blueprint.max_bulk_request_length + + drs_info = { + "maxBulkRequestLength": max_bulk, + } + if object_count is not None: + drs_info["objectCount"] = object_count + if total_object_size is not None: + drs_info["totalObjectSize"] = total_object_size + + ret["drs"] = drs_info + + # Backward compat: root-level maxBulkRequestLength (deprecated in DRS 1.5) + ret["maxBulkRequestLength"] = max_bulk + return flask.jsonify(ret), 200 -@blueprint.route("/ga4gh/drs/v1/objects/", methods=["GET"]) +@blueprint.route( + "/ga4gh/drs/v1/objects/", + methods=["GET"], + provide_automatic_options=False, +) def get_drs_object(object_id): """ Returns a specific DRSobject with object_id @@ -64,7 +100,33 @@ def get_drs_object(object_id): return flask.jsonify(data), 200 -@blueprint.route("/ga4gh/drs/v1/objects", methods=["GET"]) +@blueprint.route( + "/ga4gh/drs/v1/objects/", + methods=["POST"], + provide_automatic_options=False, +) +def post_drs_object(object_id): + """ + Returns passport-authenticated DRS object retrieval with object_id. + Not yet supported. + """ + message = "Passport-authenticated DRS object retrieval is not yet supported." + return flask.jsonify({"msg": message}), 405 + + +@blueprint.route("/ga4gh/drs/v1/objects/", methods=["OPTIONS"]) +def get_drs_object_options(object_id): + """ + Returns a specific DRSobject metadata with object_id + """ + # Get authz based on guid + authz_metadata = resolve_single_object_auth(object_id) + return flask.jsonify(authz_metadata) + + +@blueprint.route( + "/ga4gh/drs/v1/objects", methods=["GET"], provide_automatic_options=False +) def list_drs_records(): limit = flask.request.args.get("limit") start = flask.request.args.get("start") @@ -99,10 +161,99 @@ def list_drs_records(): ret = { "drs_objects": [indexd_to_drs(record, True) for record in records], } + return flask.jsonify(ret), 200 + +@blueprint.route( + "/ga4gh/drs/v1/objects", methods=["POST"], provide_automatic_options=False +) +def post_drs_records(): + """Returns DRS objects for each provided DRS object id. + Expects 'bulk_object_ids' in request body""" + data = flask.request.get_json(force=True) + # Exit with malformed error return if missing object id + if "bulk_object_ids" not in data: + return handle_user_error("Request is malformed. Missing bulk object ids.") + ret = resolve_bulk_object_auth(id_list=data["bulk_object_ids"], auth_only=False) return flask.jsonify(ret), 200 +@blueprint.route( + "/ga4gh/drs/v1/objects", methods=["POST"], provide_automatic_options=False +) +def get_drs_objects(): + """Returns DRS objects for each provided DRS object id. + Expects 'bulk_object_ids' in request body""" + data = flask.request.get_json(force=True) + # Exit with malformed error return if missing object id + if "bulk_object_ids" not in data: + return handle_user_error("Request is malformed. Missing bulk object ids.") + ret = resolve_bulk_object_auth(id_list=data["bulk_object_ids"]) + return flask.jsonify(ret), 200 + + +@blueprint.route("/ga4gh/drs/v1/objects", methods=["OPTIONS"]) +def list_drs_records_options(): + """Returns OPTIONS metadata for each provided DRS object id (drs object id = did) + + dids: list of str object ids (ex. ['123','456']) + + A response for a call with 5 dids where 3 were successfully resolved, 2 were not found, + and 2 encountered an unexpected error would look like: + + { + "summary": { + "requested": 5, + "resolved": 3, + "unresolved": 4, + }, + "unresolved_drs_objects": [ + {"error_code": 404, "object_ids": [did3, did4]}, + {"error_code": 500, "object_ids": [did5, did6]} + ], + "resolved_drs_objects": [ + { + "drs_object_id": "did1", + "bearer_auth_issuers": ["sample"], + "passport_auth_issuers": ["sample"], + "supported_types": ["BearerAuth", "PassportAuth"] + }, + { + "drs_object_id": "did2", + "bearer_auth_issuers": ["sample"], + "passport_auth_issuers": ["sample"], + "supported_types": ["BearerAuth", "PassportAuth"] + }, + { + "drs_object_id": "did3", + "bearer_auth_issuers": [], + "passport_auth_issuers": [], + "supported_types": [] + }, + ], + } + + A malformed call (i.e. providing no did list) would result in a 400 response: + {'msg': 'Request is malformed. Missing bulk object ids.', 'status_code': 400} + """ + + # Get data from json body + data = flask.request.get_json(force=True) + + # Exit with malformed error return if missing object id key + if "bulk_object_ids" not in data: + return handle_user_error("Request is malformed. Missing bulk object ids.") + + try: + compiled_info = resolve_bulk_object_auth(id_list=data["bulk_object_ids"]) + + # If unexpected error encountered, return defaults + except Exception as err: + raise IndexdUnexpectedError(err) + + return flask.jsonify(compiled_info), 200 + + def create_drs_uri(did): """ Return ga4gh-compilant drs format uri @@ -129,9 +280,152 @@ def create_drs_uri(did): return self_uri +def resolve_single_object_auth(object_id: str) -> dict: + """Returns dict with object's authorization metadata""" + + # Extract authz metadata for object id + try: + ret = blueprint.index_driver.get_with_nonstrict_prefix(object_id) + authz_path_list = ret["authz"] + authz_metadata = copy.deepcopy(blueprint.drs_authorization_metadata) + + # Define default (empty) metadata details to return + compiled_metadata_details = { + "drs_object_id": object_id, + "supported_types": [], + "bearer_auth_issuers": [], + "passport_auth_issuers": [], + } + + # If index driver found no object auth path info, return empty authz data + if not authz_path_list: + return compiled_metadata_details + + # If auth path is for open project, just return default auth info + # Note: if multiple paths exists and one is an open project, only default info is gserviceaccount + if any(["/open" in path for path in authz_path_list]): + compiled_metadata_details["supported_types"] = ["None"] + return compiled_metadata_details + + # Extract & compile auth metadata details (for each path) + compiled_passport_auth_issuers = set() + compiled_bearer_auth_issuers = set() + for authz in authz_path_list: + authz_metadata_details = authz_metadata.get(authz, {}) + # Compile passport issuer list and remove duplicates + if "passport_auth_issuers" in authz_metadata_details: + compiled_passport_auth_issuers.update( + authz_metadata_details["passport_auth_issuers"] + ) + elif blueprint.default_passport_issuer: + compiled_passport_auth_issuers.add(blueprint.default_passport_issuer) + + # Compile bearer issuer list and remove duplicates + if "bearer_auth_issuers" in authz_metadata_details: + compiled_bearer_auth_issuers.update( + authz_metadata_details["bearer_auth_issuers"] + ) + elif blueprint.default_bearer_issuer: + compiled_bearer_auth_issuers.add(blueprint.default_bearer_issuer) + else: + logger.warning( + "Unable to determine bearer issuer - this should be configured to Fence's token issuer or in trustedIssuers!!!" + ) + # Update issuer info + compiled_metadata_details["passport_auth_issuers"] = sorted( + compiled_passport_auth_issuers + ) + compiled_metadata_details["bearer_auth_issuers"] = sorted( + compiled_bearer_auth_issuers + ) + + # Update supported_types + compiled_supported_types = [] + if compiled_passport_auth_issuers: + compiled_supported_types.append("PassportAuth") + if compiled_bearer_auth_issuers: + compiled_supported_types.append("BearerAuth") + + compiled_metadata_details["supported_types"] = sorted(compiled_supported_types) + return compiled_metadata_details + except IndexNoRecordFound as err: + raise IndexNoRecordFound(err) + except Exception as err: + raise IndexdUnexpectedError(err) + + +def resolve_bulk_object_auth(id_list: list[str], auth_only=True) -> dict: + """Returns compiled dict of authorization metadata + auth_only = True # defaults to only return resolved authorization + auth_only = False # returned resolves drs object info (auth included)""" + # Return unexpected error if unhandled issue encountered... + # Prepare return defaults + total_requested = len(id_list) + unresolved_drs_objects = [] + resolved_drs_objects = [] + missing_error_guids = [] # 404 + unexpected_error_guids = [] # 500 + summary = { + "requested": total_requested, + "resolved": 0, + "unresolved": total_requested, # nothing is resolved at the start + } + # Bulk retrieve docs from id list + docs = blueprint.index_driver.get_bulk(id_list) + doc_dids = [doc["did"] for doc in docs] + # Annotate if an original id(s) is not returned in bulk call (record as unresolved, index not found) + for i in id_list: + if i not in doc_dids: + missing_error_guids.append(i) + # Check the authz for each returned object: + resolved_count = 0 + for doc in docs: + # Resolve individual + guid = doc["did"] + if guid in missing_error_guids: + continue + try: + # If index not found error occurs here, it will be caught as an unexpected error + # becuase we already checked for missing guids before the try block. Any issue + # encountered is likely not solely tied to an index-not-found issue. + if auth_only: + resolved_info = resolve_single_object_auth(object_id=guid) + else: + resolved_info = indexd_to_drs(record=doc) + # Handle unexpected error and continue + except Exception as err: + unexpected_error_guids.append(guid) + continue + # If not unexpected error found, but we know auth is None, treat as 500 + if resolved_info is None: + unexpected_error_guids.append(guid) + continue + resolved_drs_objects.append(resolved_info) + resolved_count = resolved_count + 1 + + # Update summary counts + summary["resolved"] = resolved_count + summary["unresolved"] = total_requested - resolved_count + # Update unresolved list details + if len(missing_error_guids) > 0: + unresolved_drs_objects.append( + {"error_code": 404, "object_ids": sorted(missing_error_guids)} + ) + if len(unexpected_error_guids) > 0: + unresolved_drs_objects.append( + {"error_code": 500, "object_ids": sorted(unexpected_error_guids)} + ) + # Update compiled results + compiled_info = {} + compiled_info["summary"] = summary + compiled_info["unresolved_drs_objects"] = unresolved_drs_objects + compiled_info["resolved_drs_objects"] = resolved_drs_objects + return compiled_info + + def indexd_to_drs(record, expand=False): """ - Convert record to ga4gh-compilant format + Convert record to ga4gh-compilant format. Includes access_methods resolution. Args: record(dict): json object record @@ -141,9 +435,7 @@ def indexd_to_drs(record, expand=False): did = ( record["id"] if "id" in record - else record["did"] - if "did" in record - else record["bundle_id"] + else record["did"] if "did" in record else record["bundle_id"] ) self_uri = create_drs_uri(did) @@ -157,9 +449,7 @@ def indexd_to_drs(record, expand=False): version = ( record["version"] if "version" in record - else record["rev"] - if "rev" in record - else "" + else record["rev"] if "rev" in record else "" ) index_updated_time = ( @@ -177,11 +467,10 @@ def indexd_to_drs(record, expand=False): alias = ( record["alias"] if "alias" in record - else json.loads(record["aliases"]) - if "aliases" in record - else [] + else json.loads(record["aliases"]) if "aliases" in record else [] ) + # Define current drs_object dict description drs_object = { "id": did, "mime_type": "application/json", @@ -198,10 +487,36 @@ def indexd_to_drs(record, expand=False): "checksums": [], "description": description, } - - if "description" in record: - drs_object["description"] = record["description"] - + # Get access method dict for each url + bucket_regions = get_bucket_regions() + + region = {} + urls_metadata = record.get("urls_metadata", {}) + for url, meta in urls_metadata.items(): + if isinstance(meta, dict) and meta.get("region"): + region[url] = meta["region"] + + if "urls" in record and record["urls"]: + for url in record["urls"]: + if url.startswith("s3://") and url not in region: + bucket_name = url.split("/")[2] + matched_region = lookup_bucket_region(bucket_name, bucket_regions) + if matched_region: + region[url] = matched_region + + available = {} + + for url, url_meta in record.get("urls_metadata", {}).items(): + if isinstance(url_meta, dict) and "available" in url_meta: + value = url_meta["available"] + if isinstance(value, bool): + available[url] = value + elif isinstance(value, str): + available[url] = value.lower() == "true" + else: + available[url] = bool(value) + else: + available[url] = True if "bundle_data" in record: drs_object["contents"] = [] for bundle in record["bundle_data"]: @@ -210,29 +525,67 @@ def indexd_to_drs(record, expand=False): bundle_object.pop("contents", None) drs_object["contents"].append(bundle_object) - # access_methods mapping if "urls" in record: - drs_object["access_methods"] = [] + # Add access_method key-value pair (required to append url info) + if "access_methods" not in drs_object: + drs_object["access_methods"] = [] for location in record["urls"]: location_type = location.split(":")[ 0 ] # (s3, gs, ftp, gsiftp, globus, htsget, https, file) + cloud = get_cloud_provider(location) drs_object["access_methods"].append( { "type": location_type, + "cloud": cloud, "access_url": {"url": location}, "access_id": location_type, - "region": "", + "available": available.get(location, True), + "region": region.get(location, ""), } ) - # parse out checksums + # Add authorization metadata to access_methods if record object id NOT a bundle + # AND drs_object['access_method'] is populated with an access url + # Auth metadata is optional for bundles + if form == "object" and drs_object["access_methods"] != []: + for entry in drs_object["access_methods"]: + # Take no action and continue to next if access_url missing + if "access_url" not in entry: + continue + # Otherwise add auth info in entry + did = record["did"] + authorizations = resolve_single_object_auth(object_id=did) + entry.update({"authorizations": authorizations}) + # Parse out checksums drs_object["checksums"] = parse_checksums(record, drs_object) return drs_object +def get_cloud_provider(location): + location_type = location.split(":")[0] + value = blueprint.cloud_provider_map.get(location_type) + + if isinstance(value, str) and value: + return value + + elif isinstance(value, dict): + parsed = urlparse(location) + location_key = f"{parsed.netloc}{parsed.path}" + + for prefix, provider in value.items(): + if location_key.startswith(prefix): + return provider + + logger.warning( + f"Unable to determine cloud provider for {location} " + f"from CLOUD_PROVIDER_MAP. Setting to None" + ) + return None + + def bundle_to_drs(record, expand=False, is_content=False): """ record(dict): json object record @@ -243,9 +596,7 @@ def bundle_to_drs(record, expand=False, is_content=False): did = ( record["id"] if "id" in record - else record["did"] - if "did" in record - else record["bundle_id"] + else record["did"] if "did" in record else record["bundle_id"] ) drs_uri = create_drs_uri(did) @@ -262,9 +613,7 @@ def bundle_to_drs(record, expand=False, is_content=False): contents = ( record["contents"] if "contents" in record - else record["bundle_data"] - if "bundle_data" in record - else [] + else record["bundle_data"] if "bundle_data" in record else [] ) if not expand and isinstance(contents, list): @@ -280,16 +629,12 @@ def bundle_to_drs(record, expand=False, is_content=False): aliases = ( record["alias"] if "alias" in record - else json.loads(record["aliases"]) - if "aliases" in record - else [] + else json.loads(record["aliases"]) if "aliases" in record else [] ) version = ( record["version"] if "version" in record - else record["rev"] - if "rev" in record - else "" + else record["rev"] if "rev" in record else "" ) # version = record["version"] if "version" in record else "" drs_object["checksums"] = parse_checksums(record, drs_object) @@ -373,9 +718,38 @@ def handle_unexpected_error(err): def get_config(setup_state): index_config = setup_state.app.config["INDEX"] blueprint.index_driver = index_config["driver"] - - -@blueprint.record -def get_config(setup_state): + blueprint.default_passport_issuer = None + blueprint.default_bearer_issuer = None if "DRS_SERVICE_INFO" in setup_state.app.config: blueprint.service_info = setup_state.app.config["DRS_SERVICE_INFO"] + if "DRS_AUTHORIZATION_METADATA" in setup_state.app.config: + blueprint.drs_authorization_metadata = setup_state.app.config[ + "DRS_AUTHORIZATION_METADATA" + ] + else: + logger.warning( + "DRS_AUTHORIZATION_METADATA not configured. Unable to provide authorization metadata for acces_methods in DrsObjects!!!" + ) + + if "DEFAULT_BEARER_ISSUER" in setup_state.app.config: + blueprint.default_bearer_issuer = setup_state.app.config[ + "DEFAULT_BEARER_ISSUER" + ] + else: + logger.warning( + "No default bearer issuer configured. This should be set to the Fence token issuer!!!" + ) + if "DEFAULT_PASSPORT_ISSUER" in setup_state.app.config: + blueprint.default_passport_issuer = setup_state.app.config[ + "DEFAULT_PASSPORT_ISSUER" + ] + if "CLOUD_PROVIDER_MAP" in setup_state.app.config: + blueprint.cloud_provider_map = setup_state.app.config["CLOUD_PROVIDER_MAP"] + else: + logger.warning( + "CLOUD_PROVIDER_MAP not configured. Unable to derive cloud providers from URLs" + ) + + blueprint.max_bulk_request_length = setup_state.app.config.get( + "MAX_BULK_REQUEST_LENGTH", 100 + ) diff --git a/indexd/index/blueprint.py b/indexd/index/blueprint.py index 50cadc4d5..f9c882f3d 100644 --- a/indexd/index/blueprint.py +++ b/indexd/index/blueprint.py @@ -10,6 +10,8 @@ from indexd.errors import AuthError, AuthzError from indexd.errors import UserError +from indexd.utils import get_bucket_regions, lookup_bucket_region + from .schema import PUT_RECORD_SCHEMA from .schema import POST_RECORD_SCHEMA from .schema import RECORD_ALIAS_SCHEMA @@ -31,6 +33,7 @@ blueprint.config = dict() blueprint.index_driver = None blueprint.dist = [] +blueprint.cloud_provider_map = {} ACCEPTABLE_HASHES = { "md5": re.compile(r"^[0-9a-f]{32}$").match, @@ -374,6 +377,23 @@ def get_index_record(record): Returns a record. """ ret = blueprint.index_driver.get_with_nonstrict_prefix(record) + urls_meta = ret.get("urls_metadata", []) + if urls_meta: + for location, metadata in urls_meta.items(): + location_type = location.split(":")[0] + cloud = blueprint.cloud_provider_map.get(location_type) + urls_meta[location]["cloud"] = cloud + if "region" not in metadata.keys(): + bucket_regions = get_bucket_regions() + bucket_name = metadata.get("bucket") + if bucket_name: + region = lookup_bucket_region(bucket_name, bucket_regions) + urls_meta[location]["region"] = region + + if "available" not in metadata.keys(): + urls_meta[location][ + "available" + ] = True # default to True if not specified return flask.jsonify(ret), 200 @@ -851,3 +871,9 @@ def get_config(setup_state): blueprint.index_driver = config["driver"] if "DIST" in setup_state.app.config: blueprint.dist = setup_state.app.config["DIST"] + + +@blueprint.record +def get_config(setup_state): + if "CLOUD_PROVIDER_MAP" in setup_state.app.config: + blueprint.cloud_provider_map = setup_state.app.config["CLOUD_PROVIDER_MAP"] diff --git a/indexd/index/driver.py b/indexd/index/driver.py index 975e4fe82..12967febc 100644 --- a/indexd/index/driver.py +++ b/indexd/index/driver.py @@ -74,6 +74,13 @@ def get(self, did): """ raise NotImplementedError("TODO") + @abc.abstractmethod + def get_bulk(self, did_list): + """ + Gets records for the the record ids. + """ + raise NotImplementedError("TODO") + @abc.abstractmethod def update(self, did, rev, changing_fields): """ diff --git a/indexd/urls/blueprint.py b/indexd/urls/blueprint.py index 03208bc14..f3f53ccbd 100644 --- a/indexd/urls/blueprint.py +++ b/indexd/urls/blueprint.py @@ -71,7 +71,6 @@ def query_metadata(): @blueprint.record def pre_config(state): driver = state.app.config["INDEX"]["driver"] - blueprint.logger = state.app.logger blueprint.driver = ( driver if type(driver) == SingleTableSQLAlchemyIndexDriver diff --git a/indexd/utils.py b/indexd/utils.py index 4c184e0f9..fe98ed8f7 100644 --- a/indexd/utils.py +++ b/indexd/utils.py @@ -1,6 +1,12 @@ -import logging import re from urllib.parse import urlparse +import os +import requests +from flask import current_app as app +from cdislogging import get_logger +from sqlalchemy import create_engine + +logger = get_logger(__name__) def hint_match(record, hints): @@ -10,10 +16,6 @@ def hint_match(record, hints): return False -from sqlalchemy import create_engine -from sqlalchemy.engine.reflection import Inspector - - def try_drop_test_data( user, database, root_user="postgres", host="" ): # pragma: no cover @@ -210,3 +212,47 @@ def reverse_url(url): reversed_segments = reversed(segments) res = ".".join(reversed_segments) return res + + +FENCE_SERVICE = os.environ.get("FENCE_SERVICE_URL", "http://fence-service") + + +def lookup_bucket_region(bucket_name, bucket_regions): + """ + Resolve a bucket name to a region. + Exact match first, then simple prefix fallback. + """ + if bucket_name in bucket_regions: + return bucket_regions[bucket_name] + + # remove regexp for prefix matching to remove snyk vulnerability + for pattern, region in bucket_regions.items(): + if pattern.endswith(".*") and bucket_name.startswith(pattern[:-2]): + return region + + return "" + + +def get_bucket_regions(): + cached = getattr(app, "cache", None) + if cached: + hit = cached.get("bucket_regions") + if hit: + return hit + + url = f"{FENCE_SERVICE}/data/buckets" + data = {} + + try: + resp = requests.get(url) + resp.raise_for_status() + data = resp.json().get("S3_BUCKETS") or {} + except Exception as e: + logger.warning(f"Failed to fetch bucket regions from Fence: {e}") + + regions = {k: v.get("region", "") for k, v in data.items()} + + if cached: + cached.set("bucket_regions", regions) + + return regions diff --git a/openapis/swagger.yaml b/openapis/swagger.yaml index 0472af668..4c0870dd5 100644 --- a/openapis/swagger.yaml +++ b/openapis/swagger.yaml @@ -1018,6 +1018,28 @@ paths: type: string tags: - DOS + '/ga4gh/drs/v1/service-info': + get: + tags: + - DRS + summary: Get DRS service information + description: >- + Returns DRS 1.5 compliant service information including version, + organization, bulk request limits, and aggregate statistics + (object count and total size). + operationId: GetServiceInfo + produces: + - application/json + responses: + '200': + description: DRS service information + schema: + $ref: '#/definitions/DrsServiceInfo' + '500': + description: An unexpected error occurred + schema: + $ref: '#/definitions/Error' + security: [] '/ga4gh/drs/v1/objects': get: summary: List all DrsObject. @@ -1385,6 +1407,72 @@ securityDefinitions: name: Authorization in: header definitions: + DrsServiceInfo: + type: object + required: + - id + - name + - version + - type + - organization + - drs + - maxBulkRequestLength + properties: + id: + type: string + description: Unique ID of the service (reverse domain name) + example: "io.gen3.mycommons" + name: + type: string + description: Human-readable name of the service + example: "DRS System" + version: + type: string + description: Version of the DRS service + example: "1.5.0" + type: + type: object + properties: + group: + type: string + example: "org.ga4gh" + artifact: + type: string + example: "drs" + version: + type: string + example: "1.5.0" + organization: + type: object + properties: + name: + type: string + example: "CTDS" + url: + type: string + example: "https://mycommons.org" + maxBulkRequestLength: + type: integer + description: "(Deprecated in DRS 1.5) Maximum number of objects in a single bulk request" + example: 100 + drs: + type: object + required: + - maxBulkRequestLength + properties: + maxBulkRequestLength: + type: integer + description: Maximum number of objects in a single bulk DRS request (>= 1) + example: 100 + objectCount: + type: integer + description: Total number of objects in the DRS service + example: 774560 + totalObjectSize: + type: integer + format: int64 + description: Total size in bytes of all objects in the DRS service + example: 4018437188907752 GetGUIDsOutput: type: object properties: diff --git a/poetry.lock b/poetry.lock index ec2ae2219..4944b553c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "alembic" @@ -52,29 +52,30 @@ files = [ [[package]] name = "authlib" -version = "1.6.9" +version = "1.7.2" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3"}, - {file = "authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04"}, + {file = "authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f"}, + {file = "authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231"}, ] [package.dependencies] cryptography = "*" +joserfc = ">=1.6.0" [[package]] name = "authutils" -version = "7.2.5" +version = "7.2.6" description = "Gen3 auth utility functions" optional = false python-versions = "<4.0,>=3.13" groups = ["main"] files = [ - {file = "authutils-7.2.5-py3-none-any.whl", hash = "sha256:96c627a99e0ab15c834043ffa23371cc1670dcaf4a00a0428a447ecbedffa9b1"}, - {file = "authutils-7.2.5.tar.gz", hash = "sha256:48cb0bbfc3338cf8cd4a25fa14bb7f8986d00ebf5db46565ceb2a85b43e8bf57"}, + {file = "authutils-7.2.6-py3-none-any.whl", hash = "sha256:719c0585a012b0eb3b637f21ecced643d7bed432339350e2cdb3af5e54dd9691"}, + {file = "authutils-7.2.6.tar.gz", hash = "sha256:956066f52b0dfb28971e015749078a4b1c935b277e544268c659819f6e7b3019"}, ] [package.dependencies] @@ -130,6 +131,18 @@ files = [ {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, ] +[[package]] +name = "cachelib" +version = "0.13.0" +description = "A collection of cache libraries in the same API interface." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516"}, + {file = "cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48"}, +] + [[package]] name = "cdiserrors" version = "2.1.0" @@ -163,14 +176,14 @@ files = [ [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.5.20" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, - {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, + {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, + {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, ] [[package]] @@ -273,153 +286,153 @@ pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" -version = "3.4.6" +version = "3.4.7" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6"}, - {file = "charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4"}, - {file = "charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb"}, - {file = "charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389"}, - {file = "charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f"}, - {file = "charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4"}, - {file = "charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:659a1e1b500fac8f2779dd9e1570464e012f43e580371470b45277a27baa7532"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f61aa92e4aad0be58eb6eb4e0c21acf32cf8065f4b2cae5665da756c4ceef982"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f50498891691e0864dc3da965f340fada0771f6142a378083dc4608f4ea513e2"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf625105bb9eef28a56a943fec8c8a98aeb80e7d7db99bd3c388137e6eb2d237"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2bd9d128ef93637a5d7a6af25363cf5dec3fa21cf80e68055aad627f280e8afa"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:d08ec48f0a1c48d75d0356cea971921848fb620fdeba805b28f937e90691209f"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ed80ff870ca6de33f4d953fda4d55654b9a2b340ff39ab32fa3adbcd718f264"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f98059e4fcd3e3e4e2d632b7cf81c2faae96c43c60b569e9c621468082f1d104"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:ab30e5e3e706e3063bc6de96b118688cb10396b70bb9864a430f67df98c61ecc"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:d5f5d1e9def3405f60e3ca8232d56f35c98fb7bf581efcc60051ebf53cb8b611"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:461598cd852bfa5a61b09cae2b1c02e2efcd166ee5516e243d540ac24bfa68a7"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:71be7e0e01753a89cf024abf7ecb6bca2c81738ead80d43004d9b5e3f1244e64"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:df01808ee470038c3f8dc4f48620df7225c49c2d6639e38f96e6d6ac6e6f7b0e"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-win32.whl", hash = "sha256:69dd852c2f0ad631b8b60cfbe25a28c0058a894de5abb566619c205ce0550eae"}, - {file = "charset_normalizer-3.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:517ad0e93394ac532745129ceabdf2696b609ec9f87863d337140317ebce1c14"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31215157227939b4fb3d740cd23fe27be0439afef67b785a1eb78a3ae69cba9e"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecbbd45615a6885fe3240eb9db73b9e62518b611850fdf8ab08bd56de7ad2b17"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c45a03a4c69820a399f1dda9e1d8fbf3562eda46e7720458180302021b08f778"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e8aeb10fcbe92767f0fa69ad5a72deca50d0dca07fbde97848997d778a50c9fe"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54fae94be3d75f3e573c9a1b5402dc593de19377013c9a0e4285e3d402dd3a2a"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:2f7fdd9b6e6c529d6a2501a2d36b240109e78a8ceaef5687cfcfa2bbe671d297"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1d02209e06550bdaef34af58e041ad71b88e624f5d825519da3a3308e22687"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8bc5f0687d796c05b1e28ab0d38a50e6309906ee09375dd3aff6a9c09dd6e8f4"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ee4ec14bc1680d6b0afab9aea2ef27e26d2024f18b24a2d7155a52b60da7e833"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d1a2ee9c1499fc8f86f4521f27a973c914b211ffa87322f4ee33bb35392da2c5"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:48696db7f18afb80a068821504296eb0787d9ce239b91ca15059d1d3eaacf13b"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4f41da960b196ea355357285ad1316a00099f22d0929fe168343b99b254729c9"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:802168e03fba8bbc5ce0d866d589e4b1ca751d06edee69f7f3a19c5a9fe6b597"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-win32.whl", hash = "sha256:8761ac29b6c81574724322a554605608a9960769ea83d2c73e396f3df896ad54"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:1cf0a70018692f85172348fe06d3a4b63f94ecb055e13a00c644d368eb82e5b8"}, - {file = "charset_normalizer-3.4.6-cp39-cp39-win_arm64.whl", hash = "sha256:3516bbb8d42169de9e61b8520cbeeeb716f12f4ecfe3fd30a9919aa16c806ca8"}, - {file = "charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69"}, - {file = "charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, ] [[package]] name = "click" -version = "8.3.1" +version = "8.4.0" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, - {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, + {file = "click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81"}, + {file = "click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973"}, ] [package.dependencies] @@ -523,75 +536,68 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "cryptography" -version = "46.0.6" +version = "48.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.8" +python-versions = "!=3.9.0,!=3.9.1,>=3.9" groups = ["main"] files = [ - {file = "cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19"}, - {file = "cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738"}, - {file = "cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c"}, - {file = "cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f"}, - {file = "cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2"}, - {file = "cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124"}, - {file = "cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4"}, - {file = "cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a"}, - {file = "cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d"}, - {file = "cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736"}, - {file = "cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed"}, - {file = "cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4"}, - {file = "cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa"}, - {file = "cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58"}, - {file = "cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb"}, - {file = "cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72"}, - {file = "cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c"}, - {file = "cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f"}, - {file = "cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead"}, - {file = "cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8"}, - {file = "cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0"}, - {file = "cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b"}, - {file = "cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a"}, - {file = "cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e"}, - {file = "cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759"}, + {file = "cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c"}, + {file = "cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5"}, + {file = "cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321"}, + {file = "cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74"}, + {file = "cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4"}, + {file = "cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7"}, + {file = "cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336"}, + {file = "cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057"}, + {file = "cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae"}, + {file = "cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c"}, + {file = "cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f"}, + {file = "cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12"}, + {file = "cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a"}, + {file = "cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239"}, + {file = "cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c"}, + {file = "cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4"}, + {file = "cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd"}, + {file = "cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8"}, + {file = "cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855"}, + {file = "cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b"}, + {file = "cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13"}, + {file = "cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb"}, + {file = "cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355"}, + {file = "cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a"}, + {file = "cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920"}, ] [package.dependencies] -cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} +cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] -docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox[uv] (>=2024.4.15)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] -sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==46.0.6)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] -test-randomorder = ["pytest-randomly"] [[package]] name = "deptry" @@ -699,14 +705,14 @@ dotenv = ["python-dotenv"] [[package]] name = "gen3authz" -version = "3.1.0" +version = "3.1.1" description = "Gen3 authz client" optional = false python-versions = "<4.0,>=3.13" groups = ["main"] files = [ - {file = "gen3authz-3.1.0-py3-none-any.whl", hash = "sha256:1284cb72d4a0cf00cbd79c37028d966c2615858a23780f47b79c260e7ad22786"}, - {file = "gen3authz-3.1.0.tar.gz", hash = "sha256:b05b40275782bdb03ea2196b8ca2b0cf0b952652044f800319829c50a5f6a0ec"}, + {file = "gen3authz-3.1.1-py3-none-any.whl", hash = "sha256:5223cdf841900ca9ca3c445a8cf4d12a90f73b0cb0a4450491dca42f3c020340"}, + {file = "gen3authz-3.1.1.tar.gz", hash = "sha256:6bd36e1a6cb5b5141a9ea388ee33398fa7fd777b10f91386c5e28d946a18b505"}, ] [package.dependencies] @@ -733,85 +739,111 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.46" +version = "3.1.50" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058"}, - {file = "gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f"}, + {file = "gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9"}, + {file = "gitpython-3.1.50.tar.gz", hash = "sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +doc = ["sphinx (>=7.4.7,<8)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy (==1.18.2) ; python_version >= \"3.9\"", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "greenlet" -version = "3.3.2" +version = "3.5.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.10" groups = ["main"] 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\"" files = [ - {file = "greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7"}, - {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f"}, - {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef"}, - {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca"}, - {file = "greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f"}, - {file = "greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2"}, - {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358"}, - {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99"}, - {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be"}, - {file = "greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5"}, - {file = "greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd"}, - {file = "greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb"}, - {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070"}, - {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79"}, - {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395"}, - {file = "greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f"}, - {file = "greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643"}, - {file = "greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd"}, - {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab"}, - {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a"}, - {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b"}, - {file = "greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124"}, - {file = "greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327"}, - {file = "greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9"}, - {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506"}, - {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce"}, - {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5"}, - {file = "greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492"}, - {file = "greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71"}, - {file = "greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf"}, - {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4"}, - {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727"}, - {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e"}, - {file = "greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a"}, - {file = "greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2"}, + {file = "greenlet-3.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7eacb17a9d41538a2bc4912eba5ef13823c83cb69e4d141d0813debe7163187f"}, + {file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5cc9606aa5f4e0bde0d3bd502b44f743864c3ffa5cfa1011b1e30f5aa02366f"}, + {file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3d35f87c7253b715d13d679e0783d845910144f282cb939fe1ba4ac8616269c"}, + {file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:00929c98ec525fd9bf075875d8c5f6a983a90906cdf78a66e6de2d8e466c2a19"}, + {file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:540dae7b956209af4d70a3be35927b4055f617763771e5e84a5255bea934d2f5"}, + {file = "greenlet-3.5.1-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:001775efe7b8e758861294c7a27c28af87f3f3f1c20468a2bc618c45b346c061"}, + {file = "greenlet-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cdb691169715a9a492844a83246f090182247d1a5031dc78a403f68ba1e97"}, + {file = "greenlet-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d59e840387076a51016777a9328b3f2c427c6f9208a6e958bad251be50a648d"}, + {file = "greenlet-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:b9152fca4a6466e114aaec745ae61cba739903a109754a9d4e1262f01e9259b1"}, + {file = "greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f"}, + {file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2"}, + {file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33"}, + {file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ebeb75c81211f5c702576cf81f315e77e23cfdb2c7c6fcb9dd143e6de35c360"}, + {file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563"}, + {file = "greenlet-3.5.1-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:017a544f0385d441e88714160d089d6900ef46c9eff9d99b6715a5ef2d127747"}, + {file = "greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071"}, + {file = "greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c"}, + {file = "greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e"}, + {file = "greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523"}, + {file = "greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2"}, + {file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed"}, + {file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10"}, + {file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89101bfd5011e069be974903cb3a4e4523845e4ece2d62dcd8d358933c0ef249"}, + {file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b"}, + {file = "greenlet-3.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:e6cd99ea59dd5d89f0c956606571d79bfe6f68c9eb7f4a4083a41a7f1587edee"}, + {file = "greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207"}, + {file = "greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823"}, + {file = "greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b"}, + {file = "greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188"}, + {file = "greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b"}, + {file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a"}, + {file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283"}, + {file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce"}, + {file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135"}, + {file = "greenlet-3.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436"}, + {file = "greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd"}, + {file = "greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1"}, + {file = "greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9"}, + {file = "greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e"}, + {file = "greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07"}, + {file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea"}, + {file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2"}, + {file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c"}, + {file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c"}, + {file = "greenlet-3.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d"}, + {file = "greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0"}, + {file = "greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc"}, + {file = "greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3"}, + {file = "greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54"}, + {file = "greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad"}, + {file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e"}, + {file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986"}, + {file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f"}, + {file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e"}, + {file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de"}, + {file = "greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d"}, + {file = "greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78"}, + {file = "greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2"}, + {file = "greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541"}, + {file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de"}, + {file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64"}, + {file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0"}, + {file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5"}, + {file = "greenlet-3.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc"}, + {file = "greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368"}, + {file = "greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26"}, + {file = "greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab"}, + {file = "greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6"}, + {file = "greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed"}, + {file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244"}, + {file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c"}, + {file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c"}, + {file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd"}, + {file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62"}, + {file = "greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e"}, + {file = "greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659"}, + {file = "greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e"}, + {file = "greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a"}, + {file = "greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829"}, ] [package.extras] @@ -820,26 +852,25 @@ test = ["objgraph", "psutil", "setuptools"] [[package]] name = "gunicorn" -version = "25.3.0" +version = "26.0.0" description = "WSGI HTTP Server for UNIX" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "gunicorn-25.3.0-py3-none-any.whl", hash = "sha256:cacea387dab08cd6776501621c295a904fe8e3b7aae9a1a3cbb26f4e7ed54660"}, - {file = "gunicorn-25.3.0.tar.gz", hash = "sha256:f74e1b2f9f76f6cd1ca01198968bd2dd65830edc24b6e8e4d78de8320e2fe889"}, + {file = "gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc"}, + {file = "gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.40.3)"] -fast = ["gunicorn_h1c (>=0.6.3)"] +fast = ["gunicorn_h1c (>=0.6.5)"] gevent = ["gevent (>=24.10.1)"] http2 = ["h2 (>=4.1.0)"] setproctitle = ["setproctitle"] -testing = ["coverage", "eventlet (>=0.40.3)", "gevent (>=24.10.1)", "h2 (>=4.1.0)", "httpx[http2]", "pytest", "pytest-asyncio", "pytest-cov", "uvloop (>=0.19.0)"] +testing = ["coverage", "gevent (>=24.10.1)", "h2 (>=4.1.0)", "httpx[http2]", "pytest", "pytest-asyncio", "pytest-cov", "uvloop (>=0.19.0)"] tornado = ["tornado (>=6.5.0)"] [[package]] @@ -922,18 +953,18 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "idna" -version = "3.11" +version = "3.15" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, - {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, + {file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"}, + {file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"}, ] [package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "iniconfig" @@ -977,6 +1008,24 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "joserfc" +version = "1.6.5" +description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "joserfc-1.6.5-py3-none-any.whl", hash = "sha256:e9878a0f8243fe7b95e11fdda81374ca9f7a689e302751579d3dfdeec559675e"}, + {file = "joserfc-1.6.5.tar.gz", hash = "sha256:1482a7db78fb4602e44ed89e51b599d052e091288c7c532c5b694e20149dec48"}, +] + +[package.dependencies] +cryptography = ">=45.0.1" + +[package.extras] +drafts = ["pycryptodome"] + [[package]] name = "jsonschema" version = "3.2.0" @@ -1001,14 +1050,14 @@ format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-va [[package]] name = "mako" -version = "1.3.10" +version = "1.3.12" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"}, - {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"}, + {file = "mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9"}, + {file = "mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"}, ] [package.dependencies] @@ -1137,14 +1186,14 @@ test = ["pytest (<5.4)", "pytest-cov"] [[package]] name = "packaging" -version = "26.0" +version = "26.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, + {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, + {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, ] [[package]] @@ -1165,79 +1214,79 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "psycopg2-binary" -version = "2.9.11" +version = "2.9.12" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6fe6b47d0b42ce1c9f1fa3e35bb365011ca22e39db37074458f27921dca40f2"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c0e4262e089516603a09474ee13eabf09cb65c332277e39af68f6233911087"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:865f9945ed1b3950d968ec4690ce68c55019d79e4497366d36e090327ce7db14"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0da4de5c1ac69d94ed4364b6cbe7190c1a70d325f112ba783d83f8440285f152"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e"}, - {file = "psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908"}, - {file = "psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d"}, - {file = "psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1"}, - {file = "psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d"}, - {file = "psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20e7fb94e20b03dcc783f76c0865f9da39559dcc0c28dd1a3fce0d01902a6b9c"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bdab48575b6f870f465b397c38f1b415520e9879fdf10a53ee4f49dcbdf8a21"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9d3a9edcfbe77a3ed4bc72836d466dfce4174beb79eda79ea155cc77237ed9e8"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:44fc5c2b8fa871ce7f0023f619f1349a0aa03a0857f2c96fbc01c657dcbbdb49"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9c55460033867b4622cda1b6872edf445809535144152e5d14941ef591980edf"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2d11098a83cca92deaeaed3d58cfd150d49b3b06ee0d0852be466bf87596899e"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:691c807d94aecfbc76a14e1408847d59ff5b5906a04a23e12a89007672b9e819"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b81627b691f29c4c30a8f322546ad039c40c328373b11dff7490a3e1b517855"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:b637d6d941209e8d96a072d7977238eea128046effbf37d1d8b2c0764750017d"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:41360b01c140c2a03d346cec3280cf8a71aa07d94f3b1509fa0161c366af66b4"}, - {file = "psycopg2_binary-2.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:875039274f8a2361e5207857899706da840768e2a775bf8c65e82f60b197df02"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b818ceff717f98851a64bffd4c5eb5b3059ae280276dcecc52ac658dcf006a4"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fa0d7caca8635c56e373055094eeda3208d901d55dd0ff5abc1d4e47f82b56"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:864c261b3690e1207d14bbfe0a61e27567981b80c47a778561e49f676f7ce433"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c5ee5213445dd45312459029b8c4c0a695461eb517b753d2582315bd07995f5e"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f9cae1f848779b5b01f417e762c40d026ea93eb0648249a604728cda991dde3"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:63a3ebbd543d3d1eda088ac99164e8c5bac15293ee91f20281fd17d050aee1c4"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d6fcbba8c9fed08a73b8ac61ea79e4821e45b1e92bb466230c5e746bbf3d5256"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:36512911ebb2b60a0c3e44d0bb5048c1980aced91235d133b7874f3d1d93487c"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:8ffdb59fe88f99589e34354a130217aa1fd2d615612402d6edc8b3dbc7a44463"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a46fe069b65255df410f856d842bc235f90e22ffdf532dda625fd4213d3fd9b1"}, + {file = "psycopg2_binary-2.9.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab29414b25dcb698bf26bf213e3348abdcd07bbd5de032a5bec15bd75b298b03"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c8ce6c61bd1b1f6b9c24ee32211599f6166af2c55abb19456090a21fd16554b"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b4a9eaa6e7f4ff91bec10aa3fb296878e75187bced5cc4bafe17dc40915e1326"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c6528cefc8e50fcc6f4a107e27a672058b36cc5736d665476aeb413ba88dbb06"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4e184b1fb6072bf05388aa41c697e1b2d01b3473f107e7ec44f186a32cfd0b8"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4766ab678563054d3f1d064a4db19cc4b5f9e3a8d9018592a8285cf200c248f3"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5a0253224780c978746cb9be55a946bcdaf40fe3519c0f622924cdabdafe2c39"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0dc9228d47c46bda253d2ecd6bb93b56a9f2d7ad33b684a1fa3622bf74ffe30c"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f921f3cd87035ef7df233383011d7a53ea1d346224752c1385f1edfd790ceb6a"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d999bd982a723113c1a45b55a7a6a90d64d0ed2278020ed625c490ff7bef96c"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29d4d134bd0ab46ffb04e94aa3c5fa3ef582e9026609165e2f758ff76fc3a3be"}, + {file = "psycopg2_binary-2.9.12-cp311-cp311-win_amd64.whl", hash = "sha256:cb4a1dacdd48077150dc762a9e5ddbf32c256d66cb46f80839391aa458774936"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9fe06d93e72f1c048e731a2e3e7854a5bfaa58fc736068df90b352cefe66f03f"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40e7b28b63aaf737cb3a1edc3a9bbc9a9f4ad3dcb7152e8c1130e4050eddcb7d"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89d19a9f7899e8eb0656a2b3a08e0da04c720a06db6e0033eab5928aabe60fa9"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:612b965daee295ae2da8f8218ce1d274645dc76ef3f1abf6a0a94fd57eff876d"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b9a339b79d37c1b45f3235265f07cdeb0cb5ad7acd2ac7720a5920989c17c24e"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3471336e1acfd9c7fe507b8bad5af9317b6a89294f9eb37bd9a030bb7bebcdc6"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7af18183109e23502c8b2ae7f6926c0882766f35b5175a4cd737ad825e4d7a1b"}, + {file = "psycopg2_binary-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:398fcd4db988c7d7d3713e2b8e18939776fd3fb447052daae4f24fa39daede4c"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290"}, + {file = "psycopg2_binary-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd"}, + {file = "psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ee2d84ef5eb6c04702d2e9c372ad557fb027f26a5d82804f749dfb14c7fdd2ab"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cfa2517c94ea3af6deb46f81e1bbd884faa63e28481eb2f889989dd8d95e5f03"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:ba3df2fc42a1cfa45b72cf096d4acb2b885937eedc61461081d53538d4a82a86"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:718e1fc18edf573b02cb8aea868de8d8d33f99ce9620206aa9144b67b0985e94"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c7cb4cbf894a1d36c720d713de507952c7c58f66d30834708f03dbe5c822ccf"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:049366c6d884bdcd65d66e6ca1fdbebe670b56c6c9ba46f164e6667e90881964"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fb1828cf3da68f99e45ebce1355d65d2d12b6a78fb5dfb16247aad6bdef5f5d2"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:127467c6e476dd876634f17c3d870530e73ff454ff99bff73d36e80af28e1115"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ace94261f43850e9e79f6c56636c5e0147978ab79eda5e5e5ebf13ae146fc8fe"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a7e39a65b7d2a20e4ba2e0aaad1960b61cc2888d6ab047769f8347bd3c9ad915"}, + {file = "psycopg2_binary-2.9.12-cp39-cp39-win_amd64.whl", hash = "sha256:f625abb7020e4af3432d95342daa1aa0db3fa369eed19807aa596367ba791b10"}, + {file = "psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c"}, ] [[package]] @@ -1333,14 +1382,14 @@ files = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, - {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, + {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, + {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, ] [package.dependencies] @@ -1478,14 +1527,14 @@ files = [ [[package]] name = "requests" -version = "2.33.1" +version = "2.34.2" description = "Python HTTP for Humans." optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"}, - {file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"}, + {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"}, + {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"}, ] [package.dependencies] @@ -1759,14 +1808,14 @@ files = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, + {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, + {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] @@ -1777,14 +1826,14 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "werkzeug" -version = "3.1.7" +version = "3.1.8" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "werkzeug-3.1.7-py3-none-any.whl", hash = "sha256:4b314d81163a3e1a169b6a0be2a000a0e204e8873c5de6586f453c55688d422f"}, - {file = "werkzeug-3.1.7.tar.gz", hash = "sha256:fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351"}, + {file = "werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50"}, + {file = "werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44"}, ] [package.dependencies] @@ -1808,4 +1857,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "be9906ca4e90673dfa8fb28985fd102930cd12ca989f9c9d14a5e997438f2538" +content-hash = "4b7bc22b43034e3215d1812cd3eb44f186bc31953d6115b526c449297f77c0dd" diff --git a/pyproject.toml b/pyproject.toml index ce15190bd..94a27b9b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ gitpython = "^3.1.45" requests = "^2.32.5" setuptools = "^80.9.0" psycopg2-binary = "^2.9.11" +cachelib = "^0.13.0" [tool.poetry.group.dev.dependencies] coveralls = "^3.0.1" diff --git a/tests/default_test_settings.py b/tests/default_test_settings.py index 13b577701..ca87dd9ad 100644 --- a/tests/default_test_settings.py +++ b/tests/default_test_settings.py @@ -16,15 +16,46 @@ "type": { "group": "org.ga4gh", "artifact": "drs", - "version": "1.0.3", + "version": "1.5.0", }, - "version": "1.0.3", + "version": "1.5.0", "organization": { "name": "CTDS", "url": "https://fictitious-commons.io", }, } +CONFIG["DRS_AUTHORIZATION_METADATA"] = { + "/gen3/programs/a/projects/b": { + "supported_types": ["BearerAuth", "PassportAuth"], + "passport_auth_issuers": [ + "https://ras/foo/bar", + "https://ras/foo/bar", + "https://ras/foo/bar/bar", + ], + "bearer_auth_issuers": [ + "https://gen3.datacommons.io", + "https://gen3.datacommons.io", + "sample_url", + ], + }, + "/gen3/programs/c/projects/d": { + "supported_types": ["BearerAuth", "PassportAuth"], + "passport_auth_issuers": [ + "sample_url_c_one", + "sample_url_c_one", + "sample_url_c_two", + ], + "bearer_auth_issuers": [ + "sample_url_d_one", + "sample_url_d_one", + "sample_url_d_two", + ], + }, +} +CONFIG["DEFAULT_BEARER_ISSUER"] = "test_default" +CONFIG["DEFAULT_PASSPORT_ISSUER"] = "test_passport_default" + os.environ["PRESIGNED_FENCE_URL"] = "https://fictitious-commons.io/" os.environ["HOSTNAME"] = "fictitious-commons.io" settings = {"config": CONFIG, "auth": AUTH} @@ -34,6 +65,6 @@ psql_port = os.environ["PGPORT"] if os.environ.get("PGPORT") else "5432" # database used by the `/tests/postgres` tests -settings["config"][ - "TEST_DB" -] = "postgresql://postgres:postgres@localhost:{0}/indexd_tests".format(psql_port) +settings["config"]["TEST_DB"] = ( + "postgresql://postgres:postgres@localhost:{0}/indexd_tests".format(psql_port) +) diff --git a/tests/indexclient/test_client.py b/tests/indexclient/test_client.py index 65c1357eb..2b437204b 100644 --- a/tests/indexclient/test_client.py +++ b/tests/indexclient/test_client.py @@ -22,6 +22,8 @@ def test_instantiate(index_client): baseid=baseid, urls_metadata=urls_metadata, ) + urls_metadata["s3://url/bucket/key"]["available"] = True + urls_metadata["s3://url/bucket/key"]["cloud"] = "aws" # ACL ordering is not deterministic for some reason sorted_returned_acls = sorted(doc.acl) assert doc.size == 5 @@ -51,6 +53,7 @@ def test_create_with_metadata(index_client): metadata=metadata, urls_metadata=urls_metadata, ) + urls_metadata = {"s3://bucket/key": {"k": "v", "available": True, "cloud": "aws"}} assert doc.urls_metadata == urls_metadata assert doc.metadata == metadata @@ -167,7 +170,9 @@ def test_updating_metadata(index_client): same_doc = index_client.get(doc.did) assert same_doc.metadata is not None assert same_doc.metadata.get("dummy_field", None) == "Dummy Var" - assert same_doc.urls_metadata == {doc.urls[0]: {"a": "b"}} + assert same_doc.urls_metadata == { + doc.urls[0]: {"a": "b", "available": True, "cloud": "aws"} + } def test_updating_acl(index_client): diff --git a/tests/test_client.py b/tests/test_client.py index e69451309..a1a800579 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -10,7 +10,11 @@ def get_doc( - has_metadata=True, has_baseid=False, has_urls_metadata=False, has_version=False + has_metadata=True, + has_baseid=False, + has_urls_metadata=False, + has_version=False, + has_available=False, ): doc = { "form": "object", @@ -22,8 +26,21 @@ def get_doc( doc["metadata"] = {"project_id": "bpa-UChicago"} if has_baseid: doc["baseid"] = "e044a62c-fd60-4203-b1e5-a62d1005f027" - if has_urls_metadata: - doc["urls_metadata"] = {"s3://endpointurl/bucket/key": {"state": "uploaded"}} + if has_urls_metadata and not has_available: + doc["urls_metadata"] = { + "s3://endpointurl/bucket/key": { + "state": "uploaded", + "cloud": "aws", + } + } + if has_urls_metadata and has_available: + doc["urls_metadata"] = { + "s3://endpointurl/bucket/key": { + "state": "available", + "cloud": "aws", + "available": "true", + } + } if has_version: doc["version"] = "1" return doc @@ -1411,7 +1428,7 @@ def test_cant_update_inexistent_blank_record( def test_update_urls_metadata(client, user, combined_default_and_single_table_settings): - data = get_doc(has_urls_metadata=True) + data = get_doc(has_urls_metadata=True, has_available=True) res = client.post("/index/", json=data, headers=user) assert res.status_code == 200 rec = res.json @@ -1421,7 +1438,7 @@ def test_update_urls_metadata(client, user, combined_default_and_single_table_se rec_2 = res_2.json assert rec_2["urls_metadata"] == data["urls_metadata"] - updated = {"urls_metadata": {data["urls"][0]: {"test": "b"}}} + updated = {"urls_metadata": {data["urls"][0]: {"test": "b", "cloud": "aws"}}} res = client.put( "/index/{}?rev={}".format(rec_2["did"], rec_2["rev"]), json=updated, @@ -1432,6 +1449,11 @@ def test_update_urls_metadata(client, user, combined_default_and_single_table_se res_3 = client.get("/index/" + rec["did"]) assert res_3.status_code == 200 rec_3 = res_3.json + updated = { + "urls_metadata": { + data["urls"][0]: {"test": "b", "cloud": "aws", "available": True} + } + } assert rec_3["urls_metadata"] == updated["urls_metadata"] @@ -2874,3 +2896,27 @@ def test_check_urls_metadata_partially_missing_metadata( for key in rec["urls_metadata"]: assert key in urls + + +def test_check_cloud_field(client, user, combined_default_and_single_table_settings): + """ + Checks that the cloud field is correctly set based on the url provided + """ + data = get_doc(has_urls_metadata=True) + data["urls"] = [ + "s3://endpointurl/bucket/key", + "gs://endpointurl/bucket/key", + ] + res = client.post("/index/", json=data, headers=user) + assert res.status_code == 200 + rec = res.json + did = rec["did"] + + res = client.get("/ga4gh/drs/v1/objects/" + did, headers=user) + assert res.status_code == 200 + rec = res.json + print(rec) + clouds = [] + for i in range(len(rec["access_methods"])): + clouds.append(rec["access_methods"][i]["cloud"]) + assert "aws" in clouds diff --git a/tests/test_drs.py b/tests/test_drs.py index c41ec1bac..953d57b52 100644 --- a/tests/test_drs.py +++ b/tests/test_drs.py @@ -1,11 +1,13 @@ import flask -import json -import tests.conftest -import requests import responses from tests.default_test_settings import settings from tests.test_bundles import get_bundle_doc +from unittest.mock import patch +from indexd.utils import lookup_bucket_region +from flask import current_app +from indexd.drs.blueprint import blueprint as drs_blueprint +from indexd.drs.blueprint import get_cloud_provider def generate_presigned_url_response(did, status=200, **query_params): @@ -27,21 +29,27 @@ def generate_presigned_url_response(did, status=200, **query_params): def get_doc( has_version=True, - urls=list(), + urls=None, has_description=True, has_content_created_date=True, has_content_updated_date=True, + authz: list[str] | None = None, + urls_metadata=None, ): + if authz is None: + authz = ["/gen3/programs/a/projects/b"] doc = { "form": "object", "size": 123, "urls": ["s3://endpointurl/bucket/key"], "hashes": {"md5": "8b9942cf415384b27cadf1f4d2d682e5"}, + "authz": authz, } if has_version: doc["version"] = "1" - if urls: - doc["urls"] = urls + doc["urls"] = urls or [] + if urls_metadata: + doc["urls_metadata"] = urls_metadata if has_description: doc["description"] = "A description" if has_content_updated_date: @@ -67,7 +75,7 @@ def get_bundle(client, user, has_description=True): def test_drs_get(client, user, combined_default_and_single_table_settings): - data = get_doc() + data = get_doc(urls=["s3://test"]) res_1 = client.post("/index/", json=data, headers=user) assert res_1.status_code == 200 rec_1 = res_1.json @@ -84,6 +92,10 @@ def test_drs_get(client, user, combined_default_and_single_table_settings): assert rec_2["self_uri"] == "drs://testprefix:" + rec_1["did"].split("/")[1] # according to ga4gh DRS blobs objects are NOT supposed to have contents. Only DRS Bundle objects should include the contetnts field assert "contents" not in rec_2 + # Check that access_methods populated (detailed access_methods structure checks are in separate pytest) + assert len(rec_2["access_methods"]) > 0 + assert "authorizations" in rec_2["access_methods"][0] + assert "supported_types" in rec_2["access_methods"][0]["authorizations"] def test_drs_get_no_default(client, user, combined_default_and_single_table_settings): @@ -294,41 +306,76 @@ def test_drs_multiple_endpointurl( def test_drs_list(client, user, combined_default_and_single_table_settings): - record_length = 7 - data = get_doc() + n_objects = 2 + data = get_doc(urls=["s3://test"]) submitted_guids = [] - for _ in range(record_length): + for _ in range(n_objects): res_1 = client.post("/index/", json=data, headers=user) + assert res_1.status_code == 200 did = res_1.json["did"] submitted_guids.append(did) bundle_data = get_bundle_doc(bundles=[did]) res2 = client.post("/bundle/", json=bundle_data, headers=user) - assert res_1.status_code == 200 res_2 = client.get("/ga4gh/drs/v1/objects") assert res_2.status_code == 200 rec_2 = res_2.json - assert len(rec_2["drs_objects"]) == 2 * record_length + assert len(rec_2["drs_objects"]) == 2 * n_objects assert submitted_guids.sort() == [r["id"] for r in rec_2["drs_objects"]].sort() - + # Check that access methods in bulk call are formatted as expected + n_entries_with_access_method = 0 + entry_with_access_methods = [] + for entry in rec_2["drs_objects"]: + if "access_methods" in list(entry.keys()): + n_entries_with_access_method = n_entries_with_access_method + 1 + entry_with_access_methods.append(entry) + assert n_entries_with_access_method == n_objects + for entry in entry_with_access_methods: + assert entry["access_methods"][0] == { + "access_id": "s3", + "access_url": {"url": "s3://test"}, + "authorizations": { + "drs_object_id": entry["id"], + "supported_types": ["BearerAuth", "PassportAuth"], + "bearer_auth_issuers": ["https://gen3.datacommons.io", "sample_url"], + "passport_auth_issuers": [ + "https://ras/foo/bar", + "https://ras/foo/bar/bar", + ], + }, + "available": True, + "cloud": "aws", + "region": "", + "type": "s3", + } res_3 = client.get("/ga4gh/drs/v1/objects/?form=bundle") assert res_3.status_code == 200 rec_3 = res_3.json - assert len(rec_3["drs_objects"]) == record_length + assert len(rec_3["drs_objects"]) == n_objects res_4 = client.get("/ga4gh/drs/v1/objects/?form=object") assert res_4.status_code == 200 rec_4 = res_4.json - assert len(rec_4["drs_objects"]) == record_length - - -def test_get_drs_record_not_found( - client, user, combined_default_and_single_table_settings -): - # test exception raised at nonexistent - fake_did = "testprefix/d96bab16-c4e1-44ac-923a-04328b6fe78f" - res = client.get("/ga4gh/drs/v1/objects/" + fake_did) - assert res.status_code == 404 + assert len(rec_4["drs_objects"]) == n_objects + # Check that access methods in bulk call are formatted as expected + for entry in rec_4["drs_objects"]: + assert entry["access_methods"][0] == { + "access_id": "s3", + "access_url": {"url": "s3://test"}, + "authorizations": { + "drs_object_id": entry["id"], + "supported_types": ["BearerAuth", "PassportAuth"], + "bearer_auth_issuers": ["https://gen3.datacommons.io", "sample_url"], + "passport_auth_issuers": [ + "https://ras/foo/bar", + "https://ras/foo/bar/bar", + ], + }, + "available": True, + "cloud": "aws", + "region": "", + "type": "s3", + } def test_get_drs_with_encoded_slash( @@ -355,51 +402,43 @@ def test_get_drs_with_encoded_slash( def test_drs_service_info_endpoint(client, combined_default_and_single_table_settings): """ - Test drs service endpoint with drs service info friendly distribution information + Test drs service endpoint returns DRS 1.5 compliant response """ app = flask.Flask(__name__) - expected_info = { - "id": "io.fictitious-commons", - "name": "DRS System", - "type": { - "group": "org.ga4gh", - "artifact": "drs", - "version": "1.0.3", - }, - "version": "1.0.3", - "organization": { - "name": "CTDS", - "url": "https://fictitious-commons.io", - }, - } - res = client.get("/ga4gh/drs/v1/service-info") assert res.status_code == 200 - assert res.json == expected_info + data = res.json + + assert data["id"] == "io.fictitious-commons" + assert data["name"] == "DRS System" + assert data["type"]["group"] == "org.ga4gh" + assert data["type"]["artifact"] == "drs" + assert data["type"]["version"] == "1.5.0" + assert data["version"] == "1.5.0" + assert data["organization"]["name"] == "CTDS" + assert data["organization"]["url"] == "https://fictitious-commons.io" + + assert "drs" in data + assert "maxBulkRequestLength" in data["drs"] + assert isinstance(data["drs"]["maxBulkRequestLength"], int) + assert data["drs"]["maxBulkRequestLength"] > 0 + assert data["maxBulkRequestLength"] == data["drs"]["maxBulkRequestLength"] + + assert "objectCount" in data["drs"] + assert isinstance(data["drs"]["objectCount"], int) + assert "totalObjectSize" in data["drs"] + assert isinstance(data["drs"]["totalObjectSize"], int) def test_drs_service_info_no_information_configured( client, combined_default_and_single_table_settings ): """ - Test drs service info endpoint when dist is not configured in the indexd config file + Test drs service info endpoint when DRS_SERVICE_INFO is not configured. + Should still return DRS 1.5 compliant response with hardcoded defaults. """ - expected_info = { - "id": "io.fictitious-commons", - "name": "DRS System", - "type": { - "group": "org.ga4gh", - "artifact": "drs", - "version": "1.0.3", - }, - "version": "1.0.3", - "organization": { - "name": "CTDS", - "url": "https://fictitious-commons.io", - }, - } backup = settings["config"]["DRS_SERVICE_INFO"].copy() try: @@ -408,6 +447,458 @@ def test_drs_service_info_no_information_configured( res = client.get("/ga4gh/drs/v1/service-info") assert res.status_code == 200 - assert res.json == expected_info + data = res.json + + assert data["id"] == "io.fictitious-commons" + assert data["name"] == "DRS System" + assert data["type"]["artifact"] == "drs" + assert data["type"]["version"] == "1.5.0" + assert data["version"] == "1.5.0" + + assert "drs" in data + assert "maxBulkRequestLength" in data["drs"] + assert data["maxBulkRequestLength"] == data["drs"]["maxBulkRequestLength"] finally: settings["config"]["DRS_SERVICE_INFO"] = backup + + +def test_service_info_stats_reflect_records( + client, user, combined_default_and_single_table_settings +): + """ + Test that service-info objectCount and totalObjectSize reflect actual records. + After creating records, stats should increase accordingly. + """ + # Get baseline stats + res = client.get("/ga4gh/drs/v1/service-info") + assert res.status_code == 200 + baseline_count = res.json["drs"]["objectCount"] + baseline_size = res.json["drs"]["totalObjectSize"] + + # Create two records + doc1 = get_doc() # size=123 + doc2 = get_doc() # size=123 + res1 = client.post("/index/", json=doc1, headers=user) + assert res1.status_code == 200 + res2 = client.post("/index/", json=doc2, headers=user) + assert res2.status_code == 200 + + # Verify stats increased + res = client.get("/ga4gh/drs/v1/service-info") + assert res.status_code == 200 + data = res.json + assert data["drs"]["objectCount"] == baseline_count + 2 + assert data["drs"]["totalObjectSize"] == baseline_size + 123 + 123 + + +def test_service_info_custom_bulk_limit( + client, combined_default_and_single_table_settings +): + """ + Test that modifying max_bulk_request_length on the blueprint is reflected + in both drs.maxBulkRequestLength and root maxBulkRequestLength. + """ + original = drs_blueprint.max_bulk_request_length + try: + drs_blueprint.max_bulk_request_length = 42 + + res = client.get("/ga4gh/drs/v1/service-info") + assert res.status_code == 200 + data = res.json + + assert data["drs"]["maxBulkRequestLength"] == 42 + assert data["maxBulkRequestLength"] == 42 + finally: + drs_blueprint.max_bulk_request_length = original + + +def test_bucket_region_lookup(): + fake_bucket_regions = { + "exact-bucket": "us-east-1", + "regex-bucket-.*": "us-west-2", + } + + assert lookup_bucket_region("exact-bucket", fake_bucket_regions) == "us-east-1" + assert lookup_bucket_region("regex-bucket-123", fake_bucket_regions) == "us-west-2" + assert lookup_bucket_region("nonexistent-bucket", fake_bucket_regions) == "" + + +def test_access_method_in_drs_object(client, user): + fake_bucket_regions = { + "my-test-bucket": "us-east-1", + "another-bucket-.*": "us-west-2", + } + + with patch( + "indexd.utils.get_bucket_regions", return_value=fake_bucket_regions + ), patch( + "indexd.drs.blueprint.get_bucket_regions", return_value=fake_bucket_regions + ): + urls = [ + "s3://my-test-bucket/path/to/file", + "s3://another-bucket-phs000000-c1/path/to/file", + "gs://last-bucket/path/to/file", + ] + + doc = get_doc( + urls=urls, + urls_metadata={ + urls[1]: {"available": False}, + urls[2]: {"region": "mx-central-1"}, + }, + ) + + res = client.post("/index/", json=doc, headers=user) + assert res.status_code == 200 + did = res.json["did"] + + drs_res = client.get(f"/ga4gh/drs/v1/objects/{did}") + assert drs_res.status_code == 200 + drs_json = drs_res.json + + # Build actual dict keyed by URL + actual = { + m["access_url"]["url"]: { + "region": m.get("region"), + "available": m.get("available"), + "cloud": m.get("cloud"), + } + for m in drs_json["access_methods"] + } + + expected = { + urls[0]: { + "region": "us-east-1", + "available": True, + "cloud": "aws", + }, + urls[1]: { + "region": "us-west-2", + "available": False, + "cloud": "aws", + }, + urls[2]: { + "region": "mx-central-1", + "available": True, + "cloud": "gcp", + }, + } + + for url, expected_access_method in expected.items(): + assert actual[url] == expected_access_method, { + "url": url, + "actual": actual[url], + "expected": expected_access_method, + } + + current_app.cache.clear() + + +def test_get_cloud_provider_string_mapping(): + original = drs_blueprint.cloud_provider_map + try: + drs_blueprint.cloud_provider_map = { + "s3": "aws", + "gs": "gcp", + "az": "azure", + } + + assert get_cloud_provider("s3://my-bucket/path/to/file") == "aws" + assert get_cloud_provider("gs://my-bucket/path/to/file") == "gcp" + assert get_cloud_provider("az://my-container/path/to/file") == "azure" + finally: + drs_blueprint.cloud_provider_map = original + + +def test_get_cloud_provider_unknown_protocol(): + original = drs_blueprint.cloud_provider_map + try: + drs_blueprint.cloud_provider_map = { + "s3": "aws", + "gs": "gcp", + } + + assert get_cloud_provider("ftp://example.com/path/to/file") is None + finally: + drs_blueprint.cloud_provider_map = original + + +def test_get_cloud_provider_empty_string_mapping_returns_unknown(): + original = drs_blueprint.cloud_provider_map + try: + drs_blueprint.cloud_provider_map = { + "s3": "", + } + + assert get_cloud_provider("s3://my-bucket/path/to/file") is None + finally: + drs_blueprint.cloud_provider_map = original + + +def test_get_cloud_provider_https_prefix_matching(): + original = drs_blueprint.cloud_provider_map + try: + drs_blueprint.cloud_provider_map = { + "https": { + "m3.aicommons.com/ai": "aws", + "storage.googleapis.com": "gcp", + }, + } + + assert get_cloud_provider("https://storage.googleapis.com/bucket/file") == "gcp" + + assert get_cloud_provider("https://m3.aicommons.com/ai/some/file") == "aws" + + assert get_cloud_provider("https://m3.aicommons.com/other/file") is None + + finally: + drs_blueprint.cloud_provider_map = original + + +# === Auth metadata focused tests for single object resolution === +def test_single_record_not_found( + client, user, combined_default_and_single_table_settings +): + """Tests that 404 exception raised if object id is not resolveable for single object resolution. + Both GET and OPTIONS methods should raise 404.""" + # Test exception raised at nonexistent + fake_did = "testprefix/fake_did" + res = client.get("/ga4gh/drs/v1/objects/" + fake_did) + assert res.status_code == 404 + res2 = client.options("/ga4gh/drs/v1/objects/" + fake_did) + assert res2.status_code == 404 + + +def test_single_path_not_found( + client, user, combined_default_and_single_table_settings +): + """Tests that the default auth metadata is successfully return if object id is valid (not a 404), BUT + associated path is not configured""" + # Test set up + data = get_doc(authz=["unknown/path"], urls=["s3://test"]) + doc_did = client.post("/index", json=data, headers=user).json["did"] + expected_metadata_details = { + "drs_object_id": doc_did, + "supported_types": ["BearerAuth", "PassportAuth"], + "bearer_auth_issuers": ["test_default"], + "passport_auth_issuers": ["test_passport_default"], + } + # Test GET + res_1 = client.get("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + assert "authorizations" in res1["access_methods"][0] + assert res1["access_methods"][0]["authorizations"] == expected_metadata_details + # Test OPTIONS + res_2 = client.options("ga4gh/drs/v1/objects/" + doc_did) + assert res_2._status_code == 200 + assert res_2.json == expected_metadata_details + + +def test_single_one_path(client, user, combined_default_and_single_table_settings): + # Test set up + data = get_doc(urls=["s3://test"]) + doc_did = client.post("/index", json=data, headers=user).json["did"] + expected_metadata_details = { + "drs_object_id": doc_did, + "supported_types": ["BearerAuth", "PassportAuth"], + "bearer_auth_issuers": ["https://gen3.datacommons.io", "sample_url"], + "passport_auth_issuers": ["https://ras/foo/bar", "https://ras/foo/bar/bar"], + } + # Test GET + res_1 = client.get("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + assert "authorizations" in res1["access_methods"][0] + assert res1["access_methods"][0]["authorizations"] == expected_metadata_details + # Test OPTIONS + res_1 = client.options("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + assert res1 == expected_metadata_details + + +def test_single_multi_path(client, user, combined_default_and_single_table_settings): + # # Test set up + data = get_doc( + authz=["/gen3/programs/a/projects/b", "/gen3/programs/c/projects/d"], + urls=["s3://test"], + ) + doc_did = client.post("/index", json=data, headers=user).json["did"] + expected_metadata_details = { + "drs_object_id": doc_did, + "supported_types": ["BearerAuth", "PassportAuth"], + "bearer_auth_issuers": sorted( + [ + "https://gen3.datacommons.io", + "sample_url", + "sample_url_d_one", + "sample_url_d_two", + ] + ), + "passport_auth_issuers": sorted( + [ + "https://ras/foo/bar", + "https://ras/foo/bar/bar", + "sample_url_c_one", + "sample_url_c_two", + ] + ), + } + # Test GET + res_1 = client.get("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + assert "authorizations" in res1["access_methods"][0] + assert res1["access_methods"][0]["authorizations"] == expected_metadata_details + + # Test OPTIONS + res_1 = client.options("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + assert res1 == expected_metadata_details + + +def test_single_open_path(client, user, combined_default_and_single_table_settings): + # # Test set up + data = get_doc( + authz=["/programs/open_access/projects/test", "/gen3/programs/c/projects/d"], + urls=["s3://test"], + ) + doc_did = client.post("/index", json=data, headers=user).json["did"] + expected_metadata_details = { + "drs_object_id": doc_did, + "supported_types": ["None"], + "bearer_auth_issuers": [], + "passport_auth_issuers": [], + } + # Test GET + res_1 = client.get("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + + assert "authorizations" in res1["access_methods"][0] + assert res1["access_methods"][0]["authorizations"] == expected_metadata_details + + # Test OPTIONS + res_1 = client.options("ga4gh/drs/v1/objects/" + doc_did) + assert res_1._status_code == 200 + res1 = res_1.json + assert res1 == expected_metadata_details + + +# === Auth metadata focused tests for bulk object resolution === +def bulk_response_test_setup( + user, client, n_200: int = 0, n_404: int = 0 +) -> tuple[list[str], dict[str, str], dict[str, str]]: + """Returns list of dids, expected response and 404 response dict dict for auth options testing. + Specify the number of dummy dids to create, otherwise params assumed to be 0.""" + + # Initialize variables + dids_200: list[str] = [] + dids_404: list[str] = [] + unresolved_drs_objects = [] + resolved_drs_objects: list[dict[str, str]] = [] + resolved_response_dict = { + "drs_object_id": "placeholder", + "bearer_auth_issuers": sorted(["https://gen3.datacommons.io", "sample_url"]), + "passport_auth_issuers": sorted( + ["https://ras/foo/bar", "https://ras/foo/bar/bar"] + ), + "supported_types": sorted(["BearerAuth", "PassportAuth"]), + } + + # Create resolvable dids + for n in range(0, n_200): + data = get_doc(urls=["s3://test"]) + doc_did = client.post("/index", json=data, headers=user).json["did"] + dids_200.append(doc_did) + resolved_response_dict.update({"drs_object_id": doc_did}) + resolved_drs_objects.append(dict(resolved_response_dict)) + + # Create "index not found" dids + for n in range(0, n_404): + dids_404.append("testprefix/invalid" + str(n)) + + # Define expected results (sorting lists avoids flaky ordering) + if len(dids_404) > 0: + expected_404_dict = {"error_code": 404, "object_ids": sorted(dids_404)} + unresolved_drs_objects.append(expected_404_dict) + + expected_json = { + "summary": { + "requested": n_200 + n_404, + "resolved": n_200, + "unresolved": n_404, + }, + "unresolved_drs_objects": unresolved_drs_objects, + "resolved_drs_objects": resolved_drs_objects, + } + all_dids = dids_200 + dids_404 + return all_dids, expected_json, expected_404_dict + + +def test_bulk_post(client, user, combined_default_and_single_table_settings): + """Tests endpoint for bulk POST""" + # Test set up + did_list, expected_json, expected_404_dict = bulk_response_test_setup( + user, client, n_200=2, n_404=2 + ) + # Call bulk options + data = {"bulk_object_ids": did_list} + res_1 = client.post("ga4gh/drs/v1/objects", json=data, headers=user) + + # Check results + test_json = res_1.json + assert test_json["summary"] == expected_json["summary"] + for entry in test_json["resolved_drs_objects"]: + # Check authorization info included in access methods + assert ( + entry["access_methods"][0]["authorizations"] + in expected_json["resolved_drs_objects"] + ) + # Check drsobject info (not just auth info) included + entry["form"] == "object" + entry["id"] == entry["access_methods"][0]["authorizations"]["drs_object_id"] + # Check unresolved info captured + assert len(test_json["unresolved_drs_objects"]) == len( + expected_json["unresolved_drs_objects"] + ) + assert expected_404_dict in test_json["unresolved_drs_objects"] + + +def test_bulk_options(client, user, combined_default_and_single_table_settings): + """Tests endpoint for bulk POST""" + # Test set up + did_list, expected_json, expected_404_dict = bulk_response_test_setup( + user, client, n_200=2, n_404=2 + ) + # Call bulk options + data = {"bulk_object_ids": did_list} + res_1 = client.options("ga4gh/drs/v1/objects", json=data, headers=user) + + # Check results + test_json = res_1.json + assert test_json["summary"] == expected_json["summary"] + for entry in test_json["resolved_drs_objects"]: + assert entry in expected_json["resolved_drs_objects"] + assert len(test_json["unresolved_drs_objects"]) == len( + expected_json["unresolved_drs_objects"] + ) + assert expected_404_dict in test_json["unresolved_drs_objects"] + + +def test_bulk_auth_options_malformed_error( + client, user, combined_default_and_single_table_settings +): + """Tests that bulk OPTIONS endpoint returns appropriate 'request malformed' 400 error. + Request overall is NOT successful (400) in this test scenario becuase error is fundamental + (no guids were available).""" + + # Call bulk options with missing bulk object ids key value pair + data = {} + res_1 = client.options("ga4gh/drs/v1/objects", json=data, headers=user) + + # Define expected results + assert res_1.status_code == 400 + assert res_1.json["msg"] == "Request is malformed. Missing bulk object ids." From 886428e57ac47d251bf4a5f52b25534760d17546 Mon Sep 17 00:00:00 2001 From: Kyle Burton Date: Mon, 15 Jun 2026 10:01:56 -0500 Subject: [PATCH 2/6] Updated docs, fixed single table test --- .secrets.baseline | 32 +++--- docs/drs.md | 149 +++++++++++++++++++------- docs/quickstart_helm.md | 2 +- tests/test_migrate_to_single_table.py | 2 +- 4 files changed, 129 insertions(+), 56 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 560bf1946..3f4ada549 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -205,16 +205,16 @@ { "type": "Hex High Entropy String", "filename": "docs/drs.md", - "hashed_secret": "f95801dbeb7fd8b079f5c3128e6667648f46ad78", + "hashed_secret": "62bd0c4d3a6b445b13212d23500a7f0916757c3e", "is_verified": false, - "line_number": 56 + "line_number": 130 }, { "type": "Hex High Entropy String", "filename": "docs/drs.md", "hashed_secret": "d46b814688b90bd6869a8a3b3220812e2ed48b1e", "is_verified": false, - "line_number": 80 + "line_number": 155 } ], "docs/local_dev_environment.md": [ @@ -312,7 +312,7 @@ "filename": "tests/indexclient/test_client.py", "hashed_secret": "27860ead00d0b07e86f0819703c8ea31113025a4", "is_verified": false, - "line_number": 59 + "line_number": 62 } ], "tests/postgres/migrations/test_15f2e9345ade_create_tables.py": [ @@ -394,77 +394,77 @@ "filename": "tests/test_client.py", "hashed_secret": "5666c088b494f26cd8f63ace013992f5fc391ce0", "is_verified": false, - "line_number": 19 + "line_number": 23 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "15a6d8daad1278efcaadc0d6e3d1dd2d9ebbc262", "is_verified": false, - "line_number": 1121 + "line_number": 1138 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "1b0d1a618b5c213dd792bbc3aa96ffa6bc370ef3", "is_verified": false, - "line_number": 1345 + "line_number": 1362 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "1170ace44158ff189902ff44597efef121623353", "is_verified": false, - "line_number": 1792 + "line_number": 1814 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "ff9c79b737b3ea7386618cc9437d3fb0a772182b", "is_verified": false, - "line_number": 2486 + "line_number": 2508 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "c8176f1e75e62e15dabaa4087fb7194451c8f6d2", "is_verified": false, - "line_number": 2489 + "line_number": 2511 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "d5198f8eddb1cbeb437899cd99e5ee97ab8531b4", "is_verified": false, - "line_number": 2489 + "line_number": 2511 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "02dc196562514eaa3e2feac1f441ccf6ad81e09d", "is_verified": false, - "line_number": 2493 + "line_number": 2515 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "f1cb2d91a95165a2ab909eadd9f7b65f312c7e2d", "is_verified": false, - "line_number": 2494 + "line_number": 2516 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "58db546de03270b55a4c889a5c5e6296b29fef25", "is_verified": false, - "line_number": 2495 + "line_number": 2517 }, { "type": "Hex High Entropy String", "filename": "tests/test_client.py", "hashed_secret": "b6c0bd08fde409c18760f32bef8705191840c402", "is_verified": false, - "line_number": 2496 + "line_number": 2518 } ], "tests/test_deprecated_aliases_endpoints.py": [ @@ -486,5 +486,5 @@ } ] }, - "generated_at": "2026-06-09T13:28:27Z" + "generated_at": "2026-06-15T15:01:52Z" } diff --git a/docs/drs.md b/docs/drs.md index ac7363cf3..5781a0dd4 100644 --- a/docs/drs.md +++ b/docs/drs.md @@ -29,45 +29,120 @@ The DRS API supports a variety of different content acccess policies, depending - server provides an `access_url` with the generated mechanism (e.g. a signed URL in the url field) - caller fetches the object bytes from the url (passing auth info from the specified headers, if any) +## Configuring access_method metadata +1. `cloud` is inferred from the protocol for each url defined for the DrsObject. `CLOUD_PROVIDER_MAP` environment variable can be used to detail the mapping of protocol and domain to the cloud provider. +2. `region` is determined by the region configured in fence for `S3_BUCKETS` and `GS_BUCKETS` for the bucket within each url for the DrsObjects. These can also be provided as urls_metadata for url. +3. `available` is determined by the urls_metadata for each url. Defaults to `true`. +4. `authorizations` are determined by the `DRS_AUTHORIZATION_METADATA` environment variable. This maps the IndexD record's `authz` to the issuers and supported types. If this mapping is not provided, the `DEFAULT_BEARER_ISSUER` will be used along with the `DEFAULT_PASSPORT_ISSUER` if provided. In gen3-helm, if `DEFAULT_BEARER_ISSUER` is not provided, it will default to the fence token issuer. + +**Example CLOUD_PROVIDER_MAP** +```json +{ + "s3": "aws", + "gs": "gcp", + "az": "azure", + "https": { + "m3.aicommons.com/ai": "aws", + "storage.googleapis.com": "gcp" + } +} +``` + +**Example urls_metadata** +Below is an example for using urls_metadata to provide cloud, region and available manually for a URL of a DrsObject +```json + "urls_metadata": { + "https://m3.aicommons.com/ai": { + "region": "us-east-1", + "cloud": "aws", + "available": false + } + } +``` + +**Example of DRS_AUTHORIZATION_METADATA** +Below is an example of how to configure the issuer information for a given `authz` resource. This will apply to all DrsObjects +where an underyling IndexD record's `authz` matches the resources listed in this map. + +If an IndexD record's `authz` is not listed, the `DEFAULT_BEARER_ISSUER` and `DEFAULT_PASSPORT_ISSUER` will be used. +```json +{ + "/gen3/programs/a/projects/b": { + "supported_types": ["BearerAuth", "PassportAuth"], + "passport_auth_issuers": [ + "https://ras/foo/bar", + "https://ras/foo/bar", + "https://ras/foo/bar/bar" + ], + "bearer_auth_issuers": [ + "https://gen3.datacommons.io", + "https://gen3.datacommons.io", + "sample_url" + ] + }, + "/gen3/programs/c/projects/d": { + "supported_types": ["BearerAuth", "PassportAuth"], + "passport_auth_issuers": [ + "sample_url_c_one", + "sample_url_c_one", + "sample_url_c_two" + ], + "bearer_auth_issuers": [ + "sample_url_d_one", + "sample_url_d_one", + "sample_url_d_two" + ] + } +} +``` + **Example Blob DrsObject:** ```javascript { - "access_methods": [ - { - "access_id": "gs", - "access_url": { - "url": "gs://some-bucket/File-A" - }, - "region": "", - "type": "gs" - }, - { - "access_id": "s3", - "access_url": { - "url": "s3:/some-bucket/File-A" - }, - "region": "", - "type": "s3" - } - ], - "aliases": [], - "checksums": [ - { - "checksum": "c29b922795e05b819d6d27064e636468", - "type": "md5" - } - ], - "contents": [], - "created_time": "2020-06-22T20:34:06.136066", - "description": null, - "form": "object", - "id": "dg.xxxx/01c3e7b2-2aca-47fc-b2e2-a5d7196652a5", - "mime_type": "application/json", - "name": "File-A", - "self_uri": "drs://binamb.planx-pla.net/dg.xxxx/01c3e7b2-2aca-47fc-b2e2-a5d7196652a5", - "size": 90, - "updated_time": "2020-06-22T20:34:06.136078", - "version": "838ed2d4" + "access_methods": [ + { + "access_id": "s3", + "access_url": { + "url": "s3://cdis-presigned-url-test/testdata" + }, + "authorizations": { + "bearer_auth_issuers": [ + "https://gen3.biodatacatalyst.nhlbi.nih.gov/user" + ], + "drs_object_id": "dg.4503/2ea50456-60f6-4b4c-92cc-5fc6776343ac", + "passport_auth_issuers": [ + "https://stsstg.nih.gov" + ], + "supported_types": [ + "BearerAuth", + "PassportAuth" + ] + }, + "available": true, + "cloud": "aws", + "region": "us-east-1", + "type": "s3" + } + ], + "aliases": [], + "checksums": [ + { + "checksum": "73d643ec3f4beb9020eef0beed440ad0", + "type": "md5" + } + ], + "created_time": null, + "description": null, + "form": "object", + "id": "dg.4503/2ea50456-60f6-4b4c-92cc-5fc6776343ac", + "index_created_time": "2026-06-12T18:06:22.650954", + "index_updated_time": "2026-06-12T18:06:22.650965", + "mime_type": "application/json", + "name": "test_valid", + "self_uri": "drs://PREFIX:2ea50456-60f6-4b4c-92cc-5fc6776343ac", + "size": 9, + "updated_time": null, + "version": null } ``` @@ -143,5 +218,3 @@ The DRS API supports a variety of different content acccess policies, depending "updated_time": "2020-06-22T20:39:02.578012", "version": "" } - -```` diff --git a/docs/quickstart_helm.md b/docs/quickstart_helm.md index 3bf1f6fe1..e795f6960 100644 --- a/docs/quickstart_helm.md +++ b/docs/quickstart_helm.md @@ -18,7 +18,7 @@ You can learn more about these values by accessing the indexd [README.md](https: If you would like to override any of the default values, simply copy the above values.yaml file into a local file and make any changes needed. -To deploy the service independant of other services (for testing purposes), you can set the .postgres.separate value to "true". This will deploy the service with its own instance of Postgres: +To deploy the service independent of other services (for testing purposes), you can set the .postgres.separate value to "true". This will deploy the service with its own instance of Postgres: ```bash postgres: separate: true diff --git a/tests/test_migrate_to_single_table.py b/tests/test_migrate_to_single_table.py index 6d35adf77..da7b216d0 100644 --- a/tests/test_migrate_to_single_table.py +++ b/tests/test_migrate_to_single_table.py @@ -101,7 +101,7 @@ def test_get_urls_record(): index_record_migrator = IndexRecordMigrator(creds_file="tests/test_creds.json") did = create_record()[0] result = index_record_migrator.get_urls_record(did) - assert result == ["s3://bucket/data.json", "gs://bucket/data.txt"] + assert sorted(result) == ["gs://bucket/data.txt", "s3://bucket/data.json"] def test_get_urls_metadata(): From 2673d0f2b7882edb79acddfcdb3ac226677c7185 Mon Sep 17 00:00:00 2001 From: Kyle Burton Date: Mon, 15 Jun 2026 16:18:06 -0500 Subject: [PATCH 3/6] Remove custom helm branch for integration tests --- .github/workflows/integration_tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index 4939bb566..e950ffa05 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -9,7 +9,6 @@ jobs: with: SERVICE_TO_TEST: indexd TEST_REPO_BRANCH: feat/DRS_15 - HELM_BRANCH: feat/drs_1-5-configs secrets: CI_TEST_ORCID_USERID: ${{ secrets.CI_TEST_ORCID_USERID }} CI_TEST_ORCID_PASSWORD: ${{ secrets.CI_TEST_ORCID_PASSWORD }} From 78be578851a558f33449017452a3052369898bac Mon Sep 17 00:00:00 2001 From: George Thomas Date: Thu, 18 Jun 2026 11:10:27 -0700 Subject: [PATCH 4/6] Relock to update deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 94a27b9b7..156a555d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "indexd" -version = "6.0.0" +version = "6.0.1" description = "Gen3 Indexing Service" authors = ["CTDS UChicago "] license = "Apache-2.0" From 25d8950d3742a033d298e522ae14d6ad58915e0f Mon Sep 17 00:00:00 2001 From: George Thomas Date: Mon, 22 Jun 2026 12:22:08 -0700 Subject: [PATCH 5/6] Update unit test and bump version --- indexclient/pyproject.toml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indexclient/pyproject.toml b/indexclient/pyproject.toml index 179627cac..aeb1f94c2 100644 --- a/indexclient/pyproject.toml +++ b/indexclient/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "indexclient" -version = "6.1.1" +version = "6.2.2" description = "" authors = ["CTDS UChicago "] license = "Apache-2.0" diff --git a/pyproject.toml b/pyproject.toml index 156a555d8..99e499708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "indexd" -version = "6.0.1" +version = "6.2.2" description = "Gen3 Indexing Service" authors = ["CTDS UChicago "] license = "Apache-2.0" From 24e7acb3800b6c9a4bfdb86699a32ddc4487407d Mon Sep 17 00:00:00 2001 From: Kyle Burton Date: Tue, 23 Jun 2026 17:00:45 -0500 Subject: [PATCH 6/6] Relock poetry, bump pyprojects --- indexclient/pyproject.toml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indexclient/pyproject.toml b/indexclient/pyproject.toml index aeb1f94c2..befe1804c 100644 --- a/indexclient/pyproject.toml +++ b/indexclient/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "indexclient" -version = "6.2.2" +version = "6.3.0" description = "" authors = ["CTDS UChicago "] license = "Apache-2.0" diff --git a/pyproject.toml b/pyproject.toml index 99e499708..95b1b8721 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "indexd" -version = "6.2.2" +version = "6.3.0" description = "Gen3 Indexing Service" authors = ["CTDS UChicago "] license = "Apache-2.0"