From 6afbff2d972ab7b8cce7bf62f0c6cc66a05fc732 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 17 Jul 2024 12:10:47 -0500 Subject: [PATCH 01/19] finish up AWS functionality --- .pre-commit-config.yaml | 2 +- README.md | 25 +++++++++ gen3cirrus/__init__.py | 2 + gen3cirrus/aws/__init__.py | 2 + gen3cirrus/aws/manager.py | 7 +++ gen3cirrus/aws/services.py | 27 ++++++++++ gen3cirrus/aws/utils.py | 54 ++++++++++++++++++++ gen3cirrus/core.py | 1 + gen3cirrus/errors.py | 4 +- poetry.lock | 101 ++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + test/test_utils.py | 30 +++++++++++ 12 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 gen3cirrus/aws/__init__.py create mode 100644 gen3cirrus/aws/manager.py create mode 100644 gen3cirrus/aws/services.py create mode 100644 gen3cirrus/aws/utils.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1647568c..7d22a7af 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, --branch, main, --pattern, release/.*] - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.3.0 hooks: - id: black diff --git a/README.md b/README.md index 2c0b2f85..4bd5b199 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,23 @@ using the library like above. So... you should at least read how to set up your environment. +For AWS functionality you can use an example like + +``` +import boto3 +from gen3cirrus import AwsService + +client = boto3.client() + +aws = AwsService(client) + +object = "test.txt" +bucket = "testBucket" +expiration = 3600 + +url = aws.requestorPaysDownloadPresignedURL( bucket, object, expiration) +``` + ## Setting up Environment for `cirrus` `cirrus`'s wispy clouds must dwell in the great blue expanse with other Clouds. Thus, you'll need to configure `cirrus` with necessary information about those Clouds @@ -185,6 +202,14 @@ cirrus_config.update(**settings) *Still uses Google libraries for auth* +## AWS Specific Implentation Details + +### Method for communication with AWS's API(s) + +For AWS you must bring your own Boto3 client that you have configured. + +You can then setup the AWS service and your client will be passed as an argument to the AWS api. + ## Building the Documentation - `pipenv install --dev` - `pipenv run python docs/create_docs.py` diff --git a/gen3cirrus/__init__.py b/gen3cirrus/__init__.py index 8ebde938..dbc781f1 100644 --- a/gen3cirrus/__init__.py +++ b/gen3cirrus/__init__.py @@ -1,2 +1,4 @@ # Expose public API from each cloud provider from .google_cloud import GoogleCloudManager + +from .aws import AwsCloudManager, AwsService diff --git a/gen3cirrus/aws/__init__.py b/gen3cirrus/aws/__init__.py new file mode 100644 index 00000000..7c9669ba --- /dev/null +++ b/gen3cirrus/aws/__init__.py @@ -0,0 +1,2 @@ +from .manager import AwsCloudManager +from .services import AwsService diff --git a/gen3cirrus/aws/manager.py b/gen3cirrus/aws/manager.py new file mode 100644 index 00000000..e631bdef --- /dev/null +++ b/gen3cirrus/aws/manager.py @@ -0,0 +1,7 @@ +class AwsCloudManager: + """ + Manager for AWS cloud clients. Currently this is not in use but has been added in case it is needed in the future + """ + + def __init__(self): + pass diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py new file mode 100644 index 00000000..987c581e --- /dev/null +++ b/gen3cirrus/aws/services.py @@ -0,0 +1,27 @@ +""" +Amazon service for interacting with APIs +""" + +import backoff +import boto3 +from botocore.exceptions import ClientError +from gen3cirrus.config import config +from gen3cirrus.aws.utils import generatePresignedURL, generatePresignedURLRequestorPays +from cdislogging import get_logger + +logger = get_logger(__name__, log_level="info") + + +class AwsService(object): + """ + Generic Amazon servicing using Boto3 + """ + + def __init__(self, client): + self.client = client + + def downloadPresignedURL(self, bucket, key, expiration): + return generatePresignedURL(self.client, bucket, key, expiration) + + def requestorPaysDownloadPresignedURL(self, bucket, key, expiration): + return generatePresignedURLRequestorPays(self.client, bucket, key, expiration) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py new file mode 100644 index 00000000..cd60764d --- /dev/null +++ b/gen3cirrus/aws/utils.py @@ -0,0 +1,54 @@ +import datetime +import json + +import boto3 +from botocore.exceptions import ClientError + +from cdislogging import get_logger + +logger = get_logger(__name__, log_level="info") + + +def generatePresignedURL(client, method, bucket_name, object_name, expires): + s3_client = client + + if method == "get": + m = "get_object" + elif method == "put": + m = "put_object" + else: + logger.info( + "method for generating presigned url must be 'get' for download or 'put' for upload" + ) + return None + + try: + response = s3_client.generate_presigned_url( + m, Params={"Bucket": bucket_name, "Key": object_name}, ExpiresIn=expires + ) + + except ClientError as e: + logger.info(e) + return None + + return response + + +def generatePresignedURLRequestorPays(client, bucket_name, object_name, expires): + s3_client = client + try: + response = s3_client.generate_presigned_url( + "get_object", + Params={ + "Bucket": bucket_name, + "Key": object_name, + "RequestPayer": "requester", + }, + ExpiresIn=expires, + ) + + except ClientError as e: + logger.info(e) + return None + + return response diff --git a/gen3cirrus/core.py b/gen3cirrus/core.py index 6f4ee66f..1e0300f9 100644 --- a/gen3cirrus/core.py +++ b/gen3cirrus/core.py @@ -4,6 +4,7 @@ Current Capabilities: - Manage Google resources, policies, and access (specific Google APIs are abstracted through a Management class that exposes needed behavior) +- Manage AWS resources amd access S3 APIs """ diff --git a/gen3cirrus/errors.py b/gen3cirrus/errors.py index 0dcaafda..b5ee3c16 100644 --- a/gen3cirrus/errors.py +++ b/gen3cirrus/errors.py @@ -1,5 +1,7 @@ class CirrusError(Exception): - def __init__(self, message="There was an error within the gen3cirrus library.", *args): + def __init__( + self, message="There was an error within the gen3cirrus library.", *args + ): super(CirrusError, self).__init__(message) diff --git a/poetry.lock b/poetry.lock index 6bb9ebd5..72e690f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -36,6 +36,47 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "boto3" +version = "1.34.128" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "boto3-1.34.128-py3-none-any.whl", hash = "sha256:a048ff980a81cd652724a73bc496c519b336fabe19cc8bfc6c53b2ff6eb22c7b"}, + {file = "boto3-1.34.128.tar.gz", hash = "sha256:43a6e99f53a8d34b3b4dbe424dbcc6b894350dc41a85b0af7c7bc24a7ec2cead"}, +] + +[package.dependencies] +botocore = ">=1.34.128,<1.35.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.10.0,<0.11.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.34.128" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.8" +files = [ + {file = "botocore-1.34.128-py3-none-any.whl", hash = "sha256:db67fda136c372ab3fa432580c819c89ba18d28a6152a4d2a7ea40d44082892e"}, + {file = "botocore-1.34.128.tar.gz", hash = "sha256:8d8e03f7c8c080ecafda72036eb3b482d649f8417c90b5dca33b7c2c47adb0c9"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = [ + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, + {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +crt = ["awscrt (==0.20.11)"] + [[package]] name = "cachetools" version = "5.3.2" @@ -519,6 +560,17 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + [[package]] name = "markupsafe" version = "2.1.3" @@ -744,6 +796,20 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "requests" version = "2.31.0" @@ -779,6 +845,23 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "s3transfer" +version = "0.10.1" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">= 3.8" +files = [ + {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, + {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + [[package]] name = "six" version = "1.16.0" @@ -962,6 +1045,22 @@ files = [ {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, ] +[[package]] +name = "urllib3" +version = "1.26.19" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, + {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, +] + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "urllib3" version = "2.1.0" @@ -996,4 +1095,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "8e9b33863d108fa34ba60397b732ec810f18f749adc390b4f802e510728c50f8" +content-hash = "5046c6b39cac13fc866bf6fa5ee7fbab058d304829c17bfddc27e04b1fb035e8" diff --git a/pyproject.toml b/pyproject.toml index 90f38151..97a156f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ google-cloud-storage = "*" google-api-python-client = "*" google-auth = "*" google-auth-httplib2 = "*" +boto3 = "^1.34.128" [tool.poetry.group.dev.dependencies] mock = "*" diff --git a/test/test_utils.py b/test/test_utils.py index e17f5954..791176cb 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -9,6 +9,12 @@ get_valid_service_account_id_for_user, ) +# for testing aws +import boto3 +import botocore.session +from botocore.stub import Stubber +from gen3cirrus.aws.utils import generatePresignedURL, generatePresignedURLRequestorPays + @pytest.mark.parametrize( "username", @@ -121,3 +127,27 @@ def test_get_string_to_sign_no_optional_params(): ) assert result == ("GET\n" "\n" "\n" "1388534400\n" "/bucket/objectname") + + +def test_aws_get_presigned_url(): + s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") + + bucket = "test" + obj = "test-obj.txt" + expires = 3600 + + url = generatePresignedURL(s3, "get", bucket, obj, expires) + + assert url != None + + +def test_aws_get_presigned_url_requester_pays(): + s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") + + bucket = "test" + obj = "test-obj.txt" + expires = 3600 + + url = generatePresignedURLRequestorPays(s3, bucket, obj, expires) + + assert url != None From afd876d076df915093d870616a6ae70905aa97dc Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 6 Aug 2024 09:47:19 -0500 Subject: [PATCH 02/19] general clean up and multipart upload url generation --- README.md | 2 +- gen3cirrus/aws/services.py | 36 ++- gen3cirrus/aws/utils.py | 64 ++++- poetry.lock | 488 +++++++++++++++++++------------------ pyproject.toml | 2 +- test/test_utils.py | 22 +- 6 files changed, 358 insertions(+), 256 deletions(-) diff --git a/README.md b/README.md index 4bd5b199..1dea96a6 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ cirrus_config.update(**settings) For AWS you must bring your own Boto3 client that you have configured. -You can then setup the AWS service and your client will be passed as an argument to the AWS api. +You can then setup the AWS service and your client will be passed as an argument to the AWS API. ## Building the Documentation - `pipenv install --dev` diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index 987c581e..1b873ca4 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -4,9 +4,11 @@ import backoff import boto3 + from botocore.exceptions import ClientError from gen3cirrus.config import config -from gen3cirrus.aws.utils import generatePresignedURL, generatePresignedURLRequestorPays +from gen3cirrus.aws.utils import generatePresignedURL, generatePresignedURLRequesterPays + from cdislogging import get_logger logger = get_logger(__name__, log_level="info") @@ -21,7 +23,33 @@ def __init__(self, client): self.client = client def downloadPresignedURL(self, bucket, key, expiration): - return generatePresignedURL(self.client, bucket, key, expiration) + """ + Wrapper function for generating a presingned url for downloading an object + """ + return generatePresignedURL(self.client, "get", bucket, key, expiration) + + def uploadPresignedURL(self, bucket, key, expiration): + """ + Wrapper function for generating a presingned url for uploading an object + """ + return generatePresignedURL(self.client, "put", bucket, key, expiration) + + def uploadPresignedURL(self, bucket, key, expiration): + """ + Wrapper function for generating a presingned url for uploading an object + """ + return generatePresignedURL(self.client, "put", bucket, key, expiration) + + def multipartUploadPresignedURL(self, bucket, key, expiration, upload_id, part): + """ + Wrapper function for generating a presingned url for uploading an object + """ + return generateMultipartUploadUrl( + self.client, bucket, key, expiration, upload_id, part + ) - def requestorPaysDownloadPresignedURL(self, bucket, key, expiration): - return generatePresignedURLRequestorPays(self.client, bucket, key, expiration) + def requesterPaysDownloadPresignedURL(self, bucket, key, expiration): + """ + Wrapper function for generating a presingned url for downloading an object from a requester pays bucket + """ + return generatePresignedURLRequesterPays(self.client, bucket, key, expiration) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index cd60764d..e9dc1ea7 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -10,6 +10,17 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): + """ + Function for generating a presigned url for upload or download + + Args: + client: s3 boto client + method: ["get", "put"] "get" for download and "put" for upload + bucket_name: s3 bucket name + object_name: s3 bucket object key + expires: time for presigned url to exist (in seconds) + """ + s3_client = client if method == "get": @@ -17,7 +28,7 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): elif method == "put": m = "put_object" else: - logger.info( + logger.error( "method for generating presigned url must be 'get' for download or 'put' for upload" ) return None @@ -28,13 +39,58 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): ) except ClientError as e: - logger.info(e) + logger.error(e) + return None + + return response + + +def generateMultipartUploadUrl( + client, bucket_name, object_name, expires, upload_id, part_no +): + """ + Function for generating a presigned url only for one part of multipart upload + + Args: + client: s3 boto client + method: ["get", "put"] "get" for download and "put" for upload + bucket_name: s3 bucket name + object_name: s3 bucket object key + expires: time for presigned url to exist (in seconds) + upload_id: ID for upload to s3 + part_no: part number of multipart upload + """ + s3_client = client + try: + response = s3_client.generate_presigned_url( + ClientMethod="upload_part", + Params={ + "Bucket": bucket_name, + "Key": object_name, + "UploadId": upload_id, + "PartNumber": part_no, + }, + ExpiresIn=expires, + ) + + except ClientError as e: + logger.error(e) return None return response -def generatePresignedURLRequestorPays(client, bucket_name, object_name, expires): +def generatePresignedURLRequesterPays(client, bucket_name, object_name, expires): + """ + Function for generating a presigned url only for requester pays buckets + + Args: + client: s3 boto client + method: ["get", "put"] "get" for download and "put" for upload + bucket_name: s3 bucket name + object_name: s3 bucket object key + expires: time for presigned url to exist (in seconds) + """ s3_client = client try: response = s3_client.generate_presigned_url( @@ -48,7 +104,7 @@ def generatePresignedURLRequestorPays(client, bucket_name, object_name, expires) ) except ClientError as e: - logger.info(e) + logger.error(e) return None return response diff --git a/poetry.lock b/poetry.lock index 72e690f5..db2168e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,24 +2,24 @@ [[package]] name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" +version = "0.7.16" +description = "A light, configurable Sphinx theme" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] name = "babel" -version = "2.14.0" +version = "2.15.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [package.extras] @@ -38,17 +38,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.128" +version = "1.34.154" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.128-py3-none-any.whl", hash = "sha256:a048ff980a81cd652724a73bc496c519b336fabe19cc8bfc6c53b2ff6eb22c7b"}, - {file = "boto3-1.34.128.tar.gz", hash = "sha256:43a6e99f53a8d34b3b4dbe424dbcc6b894350dc41a85b0af7c7bc24a7ec2cead"}, + {file = "boto3-1.34.154-py3-none-any.whl", hash = "sha256:7ca22adef4c77ee128e1e1dc7d48bc9512a87cc6fe3d771b3f913d5ecd41c057"}, + {file = "boto3-1.34.154.tar.gz", hash = "sha256:864f06528c583dc7b02adf12db395ecfadbf9cb0da90e907e848ffb27128ce19"}, ] [package.dependencies] -botocore = ">=1.34.128,<1.35.0" +botocore = ">=1.34.154,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -57,13 +57,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.128" +version = "1.34.154" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.128-py3-none-any.whl", hash = "sha256:db67fda136c372ab3fa432580c819c89ba18d28a6152a4d2a7ea40d44082892e"}, - {file = "botocore-1.34.128.tar.gz", hash = "sha256:8d8e03f7c8c080ecafda72036eb3b482d649f8417c90b5dca33b7c2c47adb0c9"}, + {file = "botocore-1.34.154-py3-none-any.whl", hash = "sha256:4eef4b1bb809b382ba9dc9c88f5fcc4a133f221a1acb693ee6bee4de9f325979"}, + {file = "botocore-1.34.154.tar.gz", hash = "sha256:64d9b4c85a504d77cb56dabb2ad717cd8e1717424a88edb458b01d1e5797262a"}, ] [package.dependencies] @@ -79,13 +79,13 @@ crt = ["awscrt (==0.20.11)"] [[package]] name = "cachetools" -version = "5.3.2" +version = "5.4.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, - {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, + {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, + {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, ] [[package]] @@ -100,13 +100,13 @@ files = [ [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -221,24 +221,24 @@ files = [ [[package]] name = "docutils" -version = "0.20.1" +version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] @@ -246,19 +246,20 @@ test = ["pytest (>=6)"] [[package]] name = "google-api-core" -version = "2.15.0" +version = "2.19.1" description = "Google API client core library" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-core-2.15.0.tar.gz", hash = "sha256:abc978a72658f14a2df1e5e12532effe40f94f868f6e23d95133bd6abcca35ca"}, - {file = "google_api_core-2.15.0-py3-none-any.whl", hash = "sha256:2aa56d2be495551e66bbff7f729b790546f87d5c90e74781aa77233bcb395a8a"}, + {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, + {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, ] [package.dependencies] google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" [package.extras] @@ -268,31 +269,31 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.112.0" +version = "2.139.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google-api-python-client-2.112.0.tar.gz", hash = "sha256:c3bcb5fd70d57f4c94b30c0dbeade53c216febfbf1d771eeb1a2fa74bd0d6756"}, - {file = "google_api_python_client-2.112.0-py2.py3-none-any.whl", hash = "sha256:f5e45d9812376deb7e04cda8d8ca5233aa608038bdbf1253ad8f7edcb7f6d595"}, + {file = "google_api_python_client-2.139.0-py2.py3-none-any.whl", hash = "sha256:1850a92505d91a82e2ca1635ab2b8dff179f4b67082c2651e1db332e8039840c"}, + {file = "google_api_python_client-2.139.0.tar.gz", hash = "sha256:ed4bc3abe2c060a87412465b4e8254620bbbc548eefc5388e2c5ff912d36a68b"}, ] [package.dependencies] google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" -google-auth = ">=1.19.0,<3.0.0.dev0" -google-auth-httplib2 = ">=0.1.0" -httplib2 = ">=0.15.0,<1.dev0" +google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" +google-auth-httplib2 = ">=0.2.0,<1.0.0" +httplib2 = ">=0.19.0,<1.dev0" uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.26.1" +version = "2.32.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.26.1.tar.gz", hash = "sha256:54385acca5c0fbdda510cd8585ba6f3fcb06eeecf8a6ecca39d3ee148b092590"}, - {file = "google_auth-2.26.1-py2.py3-none-any.whl", hash = "sha256:2c8b55e3e564f298122a02ab7b97458ccfcc5617840beb5d0ac757ada92c9780"}, + {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, + {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, ] [package.dependencies] @@ -342,25 +343,26 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] [[package]] name = "google-cloud-storage" -version = "2.14.0" +version = "2.18.0" description = "Google Cloud Storage API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google-cloud-storage-2.14.0.tar.gz", hash = "sha256:2d23fcf59b55e7b45336729c148bb1c464468c69d5efbaee30f7201dd90eb97e"}, - {file = "google_cloud_storage-2.14.0-py2.py3-none-any.whl", hash = "sha256:8641243bbf2a2042c16a6399551fbb13f062cbc9a2de38d6c0bb5426962e9dbd"}, + {file = "google_cloud_storage-2.18.0-py2.py3-none-any.whl", hash = "sha256:e8e1a9577952143c3fca8163005ecfadd2d70ec080fa158a8b305000e2c22fbb"}, + {file = "google_cloud_storage-2.18.0.tar.gz", hash = "sha256:0aa3f7c57f3632f81b455d91558d2b27ada96eee2de3aaa17f689db1470d9578"}, ] [package.dependencies] -google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=2.23.3,<3.0dev" +google-api-core = ">=2.15.0,<3.0.0dev" +google-auth = ">=2.26.1,<3.0dev" google-cloud-core = ">=2.3.0,<3.0dev" google-crc32c = ">=1.0,<2.0dev" google-resumable-media = ">=2.6.0" requests = ">=2.18.0,<3.0.0dev" [package.extras] -protobuf = ["protobuf (<5.0.0dev)"] +protobuf = ["protobuf (<6.0.0dev)"] +tracing = ["opentelemetry-api (>=1.1.0)"] [[package]] name = "google-crc32c" @@ -444,13 +446,13 @@ testing = ["pytest"] [[package]] name = "google-resumable-media" -version = "2.7.0" +version = "2.7.1" description = "Utilities for Google Media Downloads and Resumable Uploads" optional = false -python-versions = ">= 3.7" +python-versions = ">=3.7" files = [ - {file = "google-resumable-media-2.7.0.tar.gz", hash = "sha256:5f18f5fa9836f4b083162064a1c2c98c17239bfda9ca50ad970ccf905f3e625b"}, - {file = "google_resumable_media-2.7.0-py2.py3-none-any.whl", hash = "sha256:79543cfe433b63fd81c0844b7803aba1bb8950b47bedf7d980c38fa123937e08"}, + {file = "google-resumable-media-2.7.1.tar.gz", hash = "sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33"}, + {file = "google_resumable_media-2.7.1-py2.py3-none-any.whl", hash = "sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c"}, ] [package.dependencies] @@ -462,17 +464,17 @@ requests = ["requests (>=2.18.0,<3.0.0dev)"] [[package]] name = "googleapis-common-protos" -version = "1.62.0" +version = "1.63.2" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" files = [ - {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, - {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, ] [package.dependencies] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" [package.extras] grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] @@ -493,13 +495,13 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -515,22 +517,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "8.2.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, + {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -545,13 +547,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -573,71 +575,71 @@ files = [ [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -676,99 +678,115 @@ six = ">=1.6.1" [[package]] name = "packaging" -version = "23.2" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "pluggy" -version = "1.3.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "proto-plus" +version = "1.24.0" +description = "Beautiful, Pythonic protocol buffers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, + {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<6.0.0dev" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + [[package]] name = "protobuf" -version = "4.25.1" +version = "5.27.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, - {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, - {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, - {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, - {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, - {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, - {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, - {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, - {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, + {file = "protobuf-5.27.3-cp310-abi3-win32.whl", hash = "sha256:dcb307cd4ef8fec0cf52cb9105a03d06fbb5275ce6d84a6ae33bc6cf84e0a07b"}, + {file = "protobuf-5.27.3-cp310-abi3-win_amd64.whl", hash = "sha256:16ddf3f8c6c41e1e803da7abea17b1793a97ef079a912e42351eabb19b2cffe7"}, + {file = "protobuf-5.27.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:68248c60d53f6168f565a8c76dc58ba4fa2ade31c2d1ebdae6d80f969cdc2d4f"}, + {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b8a994fb3d1c11156e7d1e427186662b64694a62b55936b2b9348f0a7c6625ce"}, + {file = "protobuf-5.27.3-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:a55c48f2a2092d8e213bd143474df33a6ae751b781dd1d1f4d953c128a415b25"}, + {file = "protobuf-5.27.3-cp38-cp38-win32.whl", hash = "sha256:043853dcb55cc262bf2e116215ad43fa0859caab79bb0b2d31b708f128ece035"}, + {file = "protobuf-5.27.3-cp38-cp38-win_amd64.whl", hash = "sha256:c2a105c24f08b1e53d6c7ffe69cb09d0031512f0b72f812dd4005b8112dbe91e"}, + {file = "protobuf-5.27.3-cp39-cp39-win32.whl", hash = "sha256:c84eee2c71ed83704f1afbf1a85c3171eab0fd1ade3b399b3fad0884cbcca8bf"}, + {file = "protobuf-5.27.3-cp39-cp39-win_amd64.whl", hash = "sha256:af7c0b7cfbbb649ad26132e53faa348580f844d9ca46fd3ec7ca48a1ea5db8a1"}, + {file = "protobuf-5.27.3-py3-none-any.whl", hash = "sha256:8572c6533e544ebf6899c360e91d6bcbbee2549251643d32c52cf8a5de295ba5"}, + {file = "protobuf-5.27.3.tar.gz", hash = "sha256:82460903e640f2b7e34ee81a947fdaad89de796d324bcbc38ff5430bcdead82c"}, ] [[package]] name = "pyasn1" -version = "0.5.1" +version = "0.6.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, - {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, ] [[package]] name = "pyasn1-modules" -version = "0.3.0" +version = "0.4.0" description = "A collection of ASN.1-based protocols modules" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.8" files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, + {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, + {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, ] [package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" +pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyparsing" -version = "3.1.1" +version = "3.1.2" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false python-versions = ">=3.6.8" files = [ - {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, - {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, ] [package.extras] @@ -776,13 +794,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] @@ -790,11 +808,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "python-dateutil" @@ -812,13 +830,13 @@ six = ">=1.5" [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -847,13 +865,13 @@ pyasn1 = ">=0.1.3" [[package]] name = "s3transfer" -version = "0.10.1" +version = "0.10.2" description = "An Amazon S3 Transfer Manager" optional = false -python-versions = ">= 3.8" +python-versions = ">=3.8" files = [ - {file = "s3transfer-0.10.1-py3-none-any.whl", hash = "sha256:ceb252b11bcf87080fb7850a224fb6e05c8a776bab8f2b64b7f25b969464839d"}, - {file = "s3transfer-0.10.1.tar.gz", hash = "sha256:5683916b4c724f799e600f41dd9e10a9ff19871bf87623cc8f491cb4f5fa0a19"}, + {file = "s3transfer-0.10.2-py3-none-any.whl", hash = "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69"}, + {file = "s3transfer-0.10.2.tar.gz", hash = "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6"}, ] [package.dependencies] @@ -886,91 +904,86 @@ files = [ [[package]] name = "sphinx" -version = "7.2.6" +version = "7.4.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" files = [ - {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, - {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.14" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" +importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.7" +version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, - {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.5" +version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, - {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.4" +version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, - {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] @@ -989,38 +1002,34 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.6" +version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, - {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.9" +version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, - {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -1063,36 +1072,37 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "zipp" -version = "3.17.0" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "5046c6b39cac13fc866bf6fa5ee7fbab058d304829c17bfddc27e04b1fb035e8" +content-hash = "34ac5ab35ebb709cda0b8ccc8c4e296277782535c4ceb83d216323c5786442ad" diff --git a/pyproject.toml b/pyproject.toml index 97a156f5..800d63f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ google-cloud-storage = "*" google-api-python-client = "*" google-auth = "*" google-auth-httplib2 = "*" -boto3 = "^1.34.128" +boto3 = "*" [tool.poetry.group.dev.dependencies] mock = "*" diff --git a/test/test_utils.py b/test/test_utils.py index 791176cb..2b40f410 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,5 +1,8 @@ +import boto3 +import botocore.session import pytest +from botocore.stub import Stubber from unittest.mock import MagicMock, patch from urllib.parse import quote @@ -8,12 +11,10 @@ get_valid_service_account_id_for_client, get_valid_service_account_id_for_user, ) - -# for testing aws -import boto3 -import botocore.session -from botocore.stub import Stubber -from gen3cirrus.aws.utils import generatePresignedURL, generatePresignedURLRequestorPays +from gen3cirrus.aws.utils import ( + generatePresignedURL, + generatePresignedURLRequesterPays, +) @pytest.mark.parametrize( @@ -130,6 +131,10 @@ def test_get_string_to_sign_no_optional_params(): def test_aws_get_presigned_url(): + """ + Test that we can get a presigned url from a bucket + """ + s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") bucket = "test" @@ -142,12 +147,15 @@ def test_aws_get_presigned_url(): def test_aws_get_presigned_url_requester_pays(): + """ + Test that we can get a presigned url from a requester pays bucket + """ s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") bucket = "test" obj = "test-obj.txt" expires = 3600 - url = generatePresignedURLRequestorPays(s3, bucket, obj, expires) + url = generatePresignedURLRequesterPays(s3, bucket, obj, expires) assert url != None From b648ed107386409e5c91daeda548b0f036689c53 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 6 Aug 2024 11:53:44 -0500 Subject: [PATCH 03/19] debug and clean up functions --- gen3cirrus/aws/services.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index 1b873ca4..d532a865 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -34,12 +34,6 @@ def uploadPresignedURL(self, bucket, key, expiration): """ return generatePresignedURL(self.client, "put", bucket, key, expiration) - def uploadPresignedURL(self, bucket, key, expiration): - """ - Wrapper function for generating a presingned url for uploading an object - """ - return generatePresignedURL(self.client, "put", bucket, key, expiration) - def multipartUploadPresignedURL(self, bucket, key, expiration, upload_id, part): """ Wrapper function for generating a presingned url for uploading an object @@ -53,3 +47,6 @@ def requesterPaysDownloadPresignedURL(self, bucket, key, expiration): Wrapper function for generating a presingned url for downloading an object from a requester pays bucket """ return generatePresignedURLRequesterPays(self.client, bucket, key, expiration) + + def _debug(self): + print("This is for debugging purposes -- REMOVE WHEN DONE") From acda7010a323d158cc7c5034bb37095b2a5a5b20 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 14 Aug 2024 16:02:26 -0500 Subject: [PATCH 04/19] address comments and clean up --- gen3cirrus/aws/services.py | 19 ++++++++++--------- gen3cirrus/aws/utils.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index d532a865..93a7c3ef 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -7,7 +7,11 @@ from botocore.exceptions import ClientError from gen3cirrus.config import config -from gen3cirrus.aws.utils import generatePresignedURL, generatePresignedURLRequesterPays +from gen3cirrus.aws.utils import ( + generatePresignedURL, + generatePresignedURLRequesterPays, + generateMultipartUploadURL, +) from cdislogging import get_logger @@ -22,31 +26,28 @@ class AwsService(object): def __init__(self, client): self.client = client - def downloadPresignedURL(self, bucket, key, expiration): + def download_presigned_url(self, bucket, key, expiration): """ Wrapper function for generating a presingned url for downloading an object """ return generatePresignedURL(self.client, "get", bucket, key, expiration) - def uploadPresignedURL(self, bucket, key, expiration): + def upload_presigned_url(self, bucket, key, expiration): """ Wrapper function for generating a presingned url for uploading an object """ return generatePresignedURL(self.client, "put", bucket, key, expiration) - def multipartUploadPresignedURL(self, bucket, key, expiration, upload_id, part): + def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, part): """ Wrapper function for generating a presingned url for uploading an object """ - return generateMultipartUploadUrl( + return generateMultipartUploadURL( self.client, bucket, key, expiration, upload_id, part ) - def requesterPaysDownloadPresignedURL(self, bucket, key, expiration): + def requester_pays_download_presigned_url(self, bucket, key, expiration): """ Wrapper function for generating a presingned url for downloading an object from a requester pays bucket """ return generatePresignedURLRequesterPays(self.client, bucket, key, expiration) - - def _debug(self): - print("This is for debugging purposes -- REMOVE WHEN DONE") diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index e9dc1ea7..17e4df6b 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -45,7 +45,7 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): return response -def generateMultipartUploadUrl( +def generateMultipartUploadURL( client, bucket_name, object_name, expires, upload_id, part_no ): """ From f3ec9568d91393e3d0ae2303599dde8586557a46 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 15 Aug 2024 11:11:24 -0500 Subject: [PATCH 05/19] fix typos --- gen3cirrus/aws/services.py | 8 ++++---- gen3cirrus/aws/utils.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index 93a7c3ef..89e0940e 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -28,19 +28,19 @@ def __init__(self, client): def download_presigned_url(self, bucket, key, expiration): """ - Wrapper function for generating a presingned url for downloading an object + Wrapper function for generating a presigned URL for downloading an object """ return generatePresignedURL(self.client, "get", bucket, key, expiration) def upload_presigned_url(self, bucket, key, expiration): """ - Wrapper function for generating a presingned url for uploading an object + Wrapper function for generating a presigned URL for uploading an object """ return generatePresignedURL(self.client, "put", bucket, key, expiration) def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, part): """ - Wrapper function for generating a presingned url for uploading an object + Wrapper function for generating a presigned URL for uploading an object """ return generateMultipartUploadURL( self.client, bucket, key, expiration, upload_id, part @@ -48,6 +48,6 @@ def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, par def requester_pays_download_presigned_url(self, bucket, key, expiration): """ - Wrapper function for generating a presingned url for downloading an object from a requester pays bucket + Wrapper function for generating a presigned URL for downloading an object from a requester pays bucket """ return generatePresignedURLRequesterPays(self.client, bucket, key, expiration) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index 17e4df6b..32a87f8f 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -11,14 +11,14 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): """ - Function for generating a presigned url for upload or download + Function for generating a presigned URL for upload or download Args: client: s3 boto client method: ["get", "put"] "get" for download and "put" for upload bucket_name: s3 bucket name object_name: s3 bucket object key - expires: time for presigned url to exist (in seconds) + expires: time for presigned URL to exist (in seconds) """ s3_client = client @@ -29,7 +29,7 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): m = "put_object" else: logger.error( - "method for generating presigned url must be 'get' for download or 'put' for upload" + "method for generating presigned URL must be 'get' for download or 'put' for upload" ) return None @@ -49,14 +49,14 @@ def generateMultipartUploadURL( client, bucket_name, object_name, expires, upload_id, part_no ): """ - Function for generating a presigned url only for one part of multipart upload + Function for generating a presigned URL only for one part of multipart upload Args: client: s3 boto client method: ["get", "put"] "get" for download and "put" for upload bucket_name: s3 bucket name object_name: s3 bucket object key - expires: time for presigned url to exist (in seconds) + expires: time for presigned URL to exist (in seconds) upload_id: ID for upload to s3 part_no: part number of multipart upload """ @@ -82,14 +82,14 @@ def generateMultipartUploadURL( def generatePresignedURLRequesterPays(client, bucket_name, object_name, expires): """ - Function for generating a presigned url only for requester pays buckets + Function for generating a presigned URL only for requester pays buckets Args: client: s3 boto client method: ["get", "put"] "get" for download and "put" for upload bucket_name: s3 bucket name object_name: s3 bucket object key - expires: time for presigned url to exist (in seconds) + expires: time for presigned URL to exist (in seconds) """ s3_client = client try: From 16112d30342975a614622a3e9f258ad76b94a8d9 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 15 Aug 2024 16:01:42 -0500 Subject: [PATCH 06/19] adding function argument for adding additonal parameters to s3 signing --- gen3cirrus/aws/services.py | 20 ++++++++++++++------ gen3cirrus/aws/utils.py | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index 89e0940e..abd12c1b 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -26,17 +26,21 @@ class AwsService(object): def __init__(self, client): self.client = client - def download_presigned_url(self, bucket, key, expiration): + def download_presigned_url(self, bucket, key, expiration, additonal_info={}): """ Wrapper function for generating a presigned URL for downloading an object """ - return generatePresignedURL(self.client, "get", bucket, key, expiration) + return generatePresignedURL( + self.client, "get", bucket, key, expiration, additonal_info + ) - def upload_presigned_url(self, bucket, key, expiration): + def upload_presigned_url(self, bucket, key, expiration, additonal_info={}): """ Wrapper function for generating a presigned URL for uploading an object """ - return generatePresignedURL(self.client, "put", bucket, key, expiration) + return generatePresignedURL( + self.client, "put", bucket, key, expiration, additonal_info + ) def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, part): """ @@ -46,8 +50,12 @@ def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, par self.client, bucket, key, expiration, upload_id, part ) - def requester_pays_download_presigned_url(self, bucket, key, expiration): + def requester_pays_download_presigned_url( + self, bucket, key, expiration, additonal_info={} + ): """ Wrapper function for generating a presigned URL for downloading an object from a requester pays bucket """ - return generatePresignedURLRequesterPays(self.client, bucket, key, expiration) + return generatePresignedURLRequesterPays( + self.client, bucket, key, expiration, additonal_info + ) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index 32a87f8f..f1b849f1 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -9,7 +9,9 @@ logger = get_logger(__name__, log_level="info") -def generatePresignedURL(client, method, bucket_name, object_name, expires): +def generatePresignedURL( + client, method, bucket_name, object_name, expires, additional_info={} +): """ Function for generating a presigned URL for upload or download @@ -19,8 +21,16 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): bucket_name: s3 bucket name object_name: s3 bucket object key expires: time for presigned URL to exist (in seconds) + additional_info: dict of additional parameters to pass to s3 for signing """ + params = {} + params["Bucket"] = bucket_name + params["Key"] = object_name + + for key in additional_info: + params[key] = additional_info[key] + s3_client = client if method == "get": @@ -35,7 +45,9 @@ def generatePresignedURL(client, method, bucket_name, object_name, expires): try: response = s3_client.generate_presigned_url( - m, Params={"Bucket": bucket_name, "Key": object_name}, ExpiresIn=expires + m, + Params=params, + ExpiresIn=expires, ) except ClientError as e: @@ -80,7 +92,9 @@ def generateMultipartUploadURL( return response -def generatePresignedURLRequesterPays(client, bucket_name, object_name, expires): +def generatePresignedURLRequesterPays( + client, bucket_name, object_name, expires, additional_info={} +): """ Function for generating a presigned URL only for requester pays buckets @@ -90,16 +104,22 @@ def generatePresignedURLRequesterPays(client, bucket_name, object_name, expires) bucket_name: s3 bucket name object_name: s3 bucket object key expires: time for presigned URL to exist (in seconds) + additional_info: dict of additional parameters to pass to s3 for signing """ + params = {} + params["Bucket"] = bucket_name + params["Key"] = object_name + params["RequestPayer"]: "requester" + + for key in additional_info: + params[key] = additional_info[key] + s3_client = client + try: response = s3_client.generate_presigned_url( "get_object", - Params={ - "Bucket": bucket_name, - "Key": object_name, - "RequestPayer": "requester", - }, + Params=params, ExpiresIn=expires, ) From fef53197d880d3c445d76c2be797dfaa05816fa1 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 15 Aug 2024 17:23:36 -0500 Subject: [PATCH 07/19] fix requester pays params --- gen3cirrus/aws/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index f1b849f1..acb4022f 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -109,7 +109,7 @@ def generatePresignedURLRequesterPays( params = {} params["Bucket"] = bucket_name params["Key"] = object_name - params["RequestPayer"]: "requester" + params["RequestPayer"] = "requester" for key in additional_info: params[key] = additional_info[key] From e7d5d2545d3122f6c47eef8de926efa5bfdf2cb8 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 13:32:50 -0500 Subject: [PATCH 08/19] update email --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 800d63f8..530ef3fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "gen3cirrus" version = "3.0.1" description = "" -authors = ["Center for Translational Data Science at the University of Chicago "] +authors = ["Center for Translational Data Science at the University of Chicago "] license = "Apache-2.0" readme = "README.md" packages = [{include = "gen3cirrus"}] From 110d5c03feec30787b05407b3beaa46b768bfaf3 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 15:52:31 -0500 Subject: [PATCH 09/19] lower snake everything and typo fix --- README.md | 8 ++++---- gen3cirrus/aws/services.py | 30 +++++++++++++++--------------- gen3cirrus/aws/utils.py | 6 +++--- test/test_utils.py | 8 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 1dea96a6..06710f28 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ object = "test.txt" bucket = "testBucket" expiration = 3600 -url = aws.requestorPaysDownloadPresignedURL( bucket, object, expiration) +url = aws.requester_pays_download_presigned_url( bucket, object, expiration) ``` ## Setting up Environment for `cirrus` @@ -49,7 +49,7 @@ before being able to bask in its beauty. You *should* only have to do this once so don't freak out. By default, all the configurations needed by `cirrus` are assumed to be environmental -variables. You can also provide the configuration programatically in Python (instructions are later in the README). +variables. You can also provide the configuration programmatically in Python (instructions are later in the README). **Note:** This guide should cover necessary configuration, but in the effort of not having to maintain everything in two places, @@ -123,7 +123,7 @@ a few guides on settings that up, as it requires you to enable access to the Cloud Identity/GSuite API. Follow directions [here](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority) -to deletgate domain-wide authority for your service account that you're using +to delegate domain-wide authority for your service account that you're using for `GOOGLE_APPLICATION_CREDENTIALS`. For the API scopes, authorize these: @@ -158,7 +158,7 @@ GOOGLE_API_KEY="abcdefghijklmnopqrstuvwxyz" ``` ### Setting Configuration Programatically -`cirrus`, by default, reads in necessary configurations from environmental variables. You can, however, provide all these config vars programatically by calling the `update` function on the config object in `cirrus` and passing in a dictionary. +`cirrus`, by default, reads in necessary configurations from environmental variables. You can, however, provide all these config vars programmatically by calling the `update` function on the config object in `cirrus` and passing in a dictionary. For example: ``` diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index abd12c1b..e63506d7 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -8,9 +8,9 @@ from botocore.exceptions import ClientError from gen3cirrus.config import config from gen3cirrus.aws.utils import ( - generatePresignedURL, - generatePresignedURLRequesterPays, - generateMultipartUploadURL, + generate_presigned_url, + generate_presigned_url_requester_pays, + generate_multipart_upload_url, ) from cdislogging import get_logger @@ -23,39 +23,39 @@ class AwsService(object): Generic Amazon servicing using Boto3 """ - def __init__(self, client): - self.client = client + def __init__(self, boto3_client): + self.client = boto3_client - def download_presigned_url(self, bucket, key, expiration, additonal_info={}): + def download_presigned_url(self, bucket, key, expiration, additional_info={}): """ Wrapper function for generating a presigned URL for downloading an object """ - return generatePresignedURL( - self.client, "get", bucket, key, expiration, additonal_info + return generate_presigned_url( + self.client, "get", bucket, key, expiration, additional_info ) - def upload_presigned_url(self, bucket, key, expiration, additonal_info={}): + def upload_presigned_url(self, bucket, key, expiration, additional_info={}): """ Wrapper function for generating a presigned URL for uploading an object """ - return generatePresignedURL( - self.client, "put", bucket, key, expiration, additonal_info + return generate_presigned_url( + self.client, "put", bucket, key, expiration, additional_info ) def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, part): """ Wrapper function for generating a presigned URL for uploading an object """ - return generateMultipartUploadURL( + return generate_multipart_upload_url( self.client, bucket, key, expiration, upload_id, part ) def requester_pays_download_presigned_url( - self, bucket, key, expiration, additonal_info={} + self, bucket, key, expiration, additional_info={} ): """ Wrapper function for generating a presigned URL for downloading an object from a requester pays bucket """ - return generatePresignedURLRequesterPays( - self.client, bucket, key, expiration, additonal_info + return generate_presigned_url_requester_pays( + self.client, bucket, key, expiration, additional_info ) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index acb4022f..eefc0b2b 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -9,7 +9,7 @@ logger = get_logger(__name__, log_level="info") -def generatePresignedURL( +def generate_presigned_url( client, method, bucket_name, object_name, expires, additional_info={} ): """ @@ -57,7 +57,7 @@ def generatePresignedURL( return response -def generateMultipartUploadURL( +def generate_multipart_upload_url( client, bucket_name, object_name, expires, upload_id, part_no ): """ @@ -92,7 +92,7 @@ def generateMultipartUploadURL( return response -def generatePresignedURLRequesterPays( +def generate_presigned_url_requester_pays( client, bucket_name, object_name, expires, additional_info={} ): """ diff --git a/test/test_utils.py b/test/test_utils.py index 2b40f410..e2e043de 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -12,8 +12,8 @@ get_valid_service_account_id_for_user, ) from gen3cirrus.aws.utils import ( - generatePresignedURL, - generatePresignedURLRequesterPays, + generate_presigned_url, + generate_presigned_url_requester_pays, ) @@ -141,7 +141,7 @@ def test_aws_get_presigned_url(): obj = "test-obj.txt" expires = 3600 - url = generatePresignedURL(s3, "get", bucket, obj, expires) + url = generate_presigned_url(s3, "get", bucket, obj, expires) assert url != None @@ -156,6 +156,6 @@ def test_aws_get_presigned_url_requester_pays(): obj = "test-obj.txt" expires = 3600 - url = generatePresignedURLRequesterPays(s3, bucket, obj, expires) + url = generate_presigned_url_requester_pays(s3, bucket, obj, expires) assert url != None From 1f45e85f191b918f094b85b023af0b4483c635ac Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 15:54:57 -0500 Subject: [PATCH 10/19] rename single char var --- gen3cirrus/aws/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index eefc0b2b..fbffb539 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -34,9 +34,9 @@ def generate_presigned_url( s3_client = client if method == "get": - m = "get_object" + client_method = "get_object" elif method == "put": - m = "put_object" + client_method = "put_object" else: logger.error( "method for generating presigned URL must be 'get' for download or 'put' for upload" @@ -45,7 +45,7 @@ def generate_presigned_url( try: response = s3_client.generate_presigned_url( - m, + client_method, Params=params, ExpiresIn=expires, ) From 848a8342efa2221dce106be973ef5dea057c4081 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 15:57:06 -0500 Subject: [PATCH 11/19] remove unused stuff --- gen3cirrus/aws/manager.py | 7 ------- gen3cirrus/aws/services.py | 4 ---- gen3cirrus/aws/utils.py | 4 ---- 3 files changed, 15 deletions(-) delete mode 100644 gen3cirrus/aws/manager.py diff --git a/gen3cirrus/aws/manager.py b/gen3cirrus/aws/manager.py deleted file mode 100644 index e631bdef..00000000 --- a/gen3cirrus/aws/manager.py +++ /dev/null @@ -1,7 +0,0 @@ -class AwsCloudManager: - """ - Manager for AWS cloud clients. Currently this is not in use but has been added in case it is needed in the future - """ - - def __init__(self): - pass diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index e63506d7..4cb210fd 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -2,11 +2,7 @@ Amazon service for interacting with APIs """ -import backoff -import boto3 -from botocore.exceptions import ClientError -from gen3cirrus.config import config from gen3cirrus.aws.utils import ( generate_presigned_url, generate_presigned_url_requester_pays, diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index fbffb539..9be260d0 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -1,7 +1,3 @@ -import datetime -import json - -import boto3 from botocore.exceptions import ClientError from cdislogging import get_logger From dfa947861d76006bbefceefddc2c2bb1649b321e Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 15:58:54 -0500 Subject: [PATCH 12/19] dont default to dict --- gen3cirrus/aws/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index 9be260d0..dadf4e80 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -6,7 +6,7 @@ def generate_presigned_url( - client, method, bucket_name, object_name, expires, additional_info={} + client, method, bucket_name, object_name, expires, additional_info=None ): """ Function for generating a presigned URL for upload or download @@ -24,6 +24,7 @@ def generate_presigned_url( params["Bucket"] = bucket_name params["Key"] = object_name + additional_info = additional_info or {} for key in additional_info: params[key] = additional_info[key] @@ -89,7 +90,7 @@ def generate_multipart_upload_url( def generate_presigned_url_requester_pays( - client, bucket_name, object_name, expires, additional_info={} + client, bucket_name, object_name, expires, additional_info=None ): """ Function for generating a presigned URL only for requester pays buckets @@ -107,6 +108,7 @@ def generate_presigned_url_requester_pays( params["Key"] = object_name params["RequestPayer"] = "requester" + additional_info = additional_info or {} for key in additional_info: params[key] = additional_info[key] From ca79847bf09c3250aed17f4acfb3492c338a3a59 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 16:06:07 -0500 Subject: [PATCH 13/19] more clenup --- gen3cirrus/__init__.py | 2 -- gen3cirrus/aws/__init__.py | 1 - test/test_utils.py | 3 --- 3 files changed, 6 deletions(-) diff --git a/gen3cirrus/__init__.py b/gen3cirrus/__init__.py index dbc781f1..8ebde938 100644 --- a/gen3cirrus/__init__.py +++ b/gen3cirrus/__init__.py @@ -1,4 +1,2 @@ # Expose public API from each cloud provider from .google_cloud import GoogleCloudManager - -from .aws import AwsCloudManager, AwsService diff --git a/gen3cirrus/aws/__init__.py b/gen3cirrus/aws/__init__.py index 7c9669ba..1d87b976 100644 --- a/gen3cirrus/aws/__init__.py +++ b/gen3cirrus/aws/__init__.py @@ -1,2 +1 @@ -from .manager import AwsCloudManager from .services import AwsService diff --git a/test/test_utils.py b/test/test_utils.py index e2e043de..89374950 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,9 +1,6 @@ import boto3 -import botocore.session import pytest -from botocore.stub import Stubber -from unittest.mock import MagicMock, patch from urllib.parse import quote from gen3cirrus.google_cloud.utils import ( From a4e034064d63bb8401a9bafd1592b6b1c044f3fa Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 16:16:46 -0500 Subject: [PATCH 14/19] update docstrings --- gen3cirrus/aws/services.py | 10 +++++----- gen3cirrus/aws/utils.py | 38 +++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/gen3cirrus/aws/services.py b/gen3cirrus/aws/services.py index 4cb210fd..eb67b90d 100644 --- a/gen3cirrus/aws/services.py +++ b/gen3cirrus/aws/services.py @@ -16,13 +16,13 @@ class AwsService(object): """ - Generic Amazon servicing using Boto3 + Generic Amazon services using Boto3 """ def __init__(self, boto3_client): self.client = boto3_client - def download_presigned_url(self, bucket, key, expiration, additional_info={}): + def download_presigned_url(self, bucket, key, expiration, additional_info=None): """ Wrapper function for generating a presigned URL for downloading an object """ @@ -30,7 +30,7 @@ def download_presigned_url(self, bucket, key, expiration, additional_info={}): self.client, "get", bucket, key, expiration, additional_info ) - def upload_presigned_url(self, bucket, key, expiration, additional_info={}): + def upload_presigned_url(self, bucket, key, expiration, additional_info=None): """ Wrapper function for generating a presigned URL for uploading an object """ @@ -40,14 +40,14 @@ def upload_presigned_url(self, bucket, key, expiration, additional_info={}): def multipart_upload_presigned_url(self, bucket, key, expiration, upload_id, part): """ - Wrapper function for generating a presigned URL for uploading an object + Wrapper function for generating a presigned URL for uploading an object using multipart upload """ return generate_multipart_upload_url( self.client, bucket, key, expiration, upload_id, part ) def requester_pays_download_presigned_url( - self, bucket, key, expiration, additional_info={} + self, bucket, key, expiration, additional_info=None ): """ Wrapper function for generating a presigned URL for downloading an object from a requester pays bucket diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index dadf4e80..cdaf65b8 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -12,12 +12,12 @@ def generate_presigned_url( Function for generating a presigned URL for upload or download Args: - client: s3 boto client - method: ["get", "put"] "get" for download and "put" for upload - bucket_name: s3 bucket name - object_name: s3 bucket object key - expires: time for presigned URL to exist (in seconds) - additional_info: dict of additional parameters to pass to s3 for signing + client (S3.Client): boto3 S3 client + method (string): ["get", "put"] "get" for download and "put" for upload + bucket_name (string): s3 bucket name + object_name (string): s3 bucket object key + expires (int): time for presigned URL to exist (in seconds) + additional_info (dict): dict of additional parameters to pass to s3 for signing """ params = {} @@ -61,13 +61,13 @@ def generate_multipart_upload_url( Function for generating a presigned URL only for one part of multipart upload Args: - client: s3 boto client - method: ["get", "put"] "get" for download and "put" for upload - bucket_name: s3 bucket name - object_name: s3 bucket object key - expires: time for presigned URL to exist (in seconds) - upload_id: ID for upload to s3 - part_no: part number of multipart upload + client (S3.Client): boto3 S3 client + method (string): ["get", "put"] "get" for download and "put" for upload + bucket_name (string): s3 bucket name + object_name (string): s3 bucket object key + expires (int): time for presigned URL to exist (in seconds) + upload_id (string): ID for upload to s3 + part_no (int): part number of multipart upload """ s3_client = client try: @@ -96,12 +96,12 @@ def generate_presigned_url_requester_pays( Function for generating a presigned URL only for requester pays buckets Args: - client: s3 boto client - method: ["get", "put"] "get" for download and "put" for upload - bucket_name: s3 bucket name - object_name: s3 bucket object key - expires: time for presigned URL to exist (in seconds) - additional_info: dict of additional parameters to pass to s3 for signing + client (S3.Client): boto3 S3 client + method (string): ["get", "put"] "get" for download and "put" for upload + bucket_name (string): s3 bucket name + object_name (string): s3 bucket object key + expires (int): time for presigned URL to exist (in seconds) + additional_info (dict): dict of additional parameters to pass to s3 for signing """ params = {} params["Bucket"] = bucket_name From 2cd07d11a6af0c44ff48f1e0d7ed4f4c81318284 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 16:18:24 -0500 Subject: [PATCH 15/19] update version --- poetry.lock | 70 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/poetry.lock b/poetry.lock index db2168e6..e7ae9d88 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -13,13 +13,13 @@ files = [ [[package]] name = "babel" -version = "2.15.0" +version = "2.16.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" files = [ - {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, - {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] [package.extras] @@ -38,17 +38,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.154" +version = "1.35.3" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.154-py3-none-any.whl", hash = "sha256:7ca22adef4c77ee128e1e1dc7d48bc9512a87cc6fe3d771b3f913d5ecd41c057"}, - {file = "boto3-1.34.154.tar.gz", hash = "sha256:864f06528c583dc7b02adf12db395ecfadbf9cb0da90e907e848ffb27128ce19"}, + {file = "boto3-1.35.3-py3-none-any.whl", hash = "sha256:e24f9b8a4dc85bfcf4d3e9183589dba6ed2964dfa1233ca578fc7c04db1c68b4"}, + {file = "boto3-1.35.3.tar.gz", hash = "sha256:9c2799cfeba2c68d91f840b6d018ffe76f5f27514dcbb166b20d9e3a704cbcea"}, ] [package.dependencies] -botocore = ">=1.34.154,<1.35.0" +botocore = ">=1.35.3,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -57,13 +57,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.154" +version = "1.35.3" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.154-py3-none-any.whl", hash = "sha256:4eef4b1bb809b382ba9dc9c88f5fcc4a133f221a1acb693ee6bee4de9f325979"}, - {file = "botocore-1.34.154.tar.gz", hash = "sha256:64d9b4c85a504d77cb56dabb2ad717cd8e1717424a88edb458b01d1e5797262a"}, + {file = "botocore-1.35.3-py3-none-any.whl", hash = "sha256:3ff54075e125304a8978e5d3f27ab96485f2deedb561d458b0567a0a921dc243"}, + {file = "botocore-1.35.3.tar.gz", hash = "sha256:ff0c3189c0aa588c3aeda3f3e5e37925d64deaac6748310124307f978933e768"}, ] [package.dependencies] @@ -75,17 +75,17 @@ urllib3 = [ ] [package.extras] -crt = ["awscrt (==0.20.11)"] +crt = ["awscrt (==0.21.2)"] [[package]] name = "cachetools" -version = "5.4.0" +version = "5.5.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" files = [ - {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, - {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, ] [[package]] @@ -269,13 +269,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.139.0" +version = "2.142.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.139.0-py2.py3-none-any.whl", hash = "sha256:1850a92505d91a82e2ca1635ab2b8dff179f4b67082c2651e1db332e8039840c"}, - {file = "google_api_python_client-2.139.0.tar.gz", hash = "sha256:ed4bc3abe2c060a87412465b4e8254620bbbc548eefc5388e2c5ff912d36a68b"}, + {file = "google_api_python_client-2.142.0-py2.py3-none-any.whl", hash = "sha256:266799082bb8301f423ec204dffbffb470b502abbf29efd1f83e644d36eb5a8f"}, + {file = "google_api_python_client-2.142.0.tar.gz", hash = "sha256:a1101ac9e24356557ca22f07ff48b7f61fa5d4b4e7feeef3bda16e5dcb86350e"}, ] [package.dependencies] @@ -287,13 +287,13 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.32.0" +version = "2.34.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, - {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, + {file = "google_auth-2.34.0-py2.py3-none-any.whl", hash = "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65"}, + {file = "google_auth-2.34.0.tar.gz", hash = "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc"}, ] [package.dependencies] @@ -303,7 +303,7 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +enterprise-cert = ["cryptography", "pyopenssl"] pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] requests = ["requests (>=2.20.0,<3.0.0.dev0)"] @@ -343,13 +343,13 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] [[package]] name = "google-cloud-storage" -version = "2.18.0" +version = "2.18.2" description = "Google Cloud Storage API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google_cloud_storage-2.18.0-py2.py3-none-any.whl", hash = "sha256:e8e1a9577952143c3fca8163005ecfadd2d70ec080fa158a8b305000e2c22fbb"}, - {file = "google_cloud_storage-2.18.0.tar.gz", hash = "sha256:0aa3f7c57f3632f81b455d91558d2b27ada96eee2de3aaa17f689db1470d9578"}, + {file = "google_cloud_storage-2.18.2-py2.py3-none-any.whl", hash = "sha256:97a4d45c368b7d401ed48c4fdfe86e1e1cb96401c9e199e419d289e2c0370166"}, + {file = "google_cloud_storage-2.18.2.tar.gz", hash = "sha256:aaf7acd70cdad9f274d29332673fcab98708d0e1f4dceb5a5356aaef06af4d99"}, ] [package.dependencies] @@ -357,7 +357,7 @@ google-api-core = ">=2.15.0,<3.0.0dev" google-auth = ">=2.26.1,<3.0dev" google-cloud-core = ">=2.3.0,<3.0dev" google-crc32c = ">=1.0,<2.0dev" -google-resumable-media = ">=2.6.0" +google-resumable-media = ">=2.7.2" requests = ">=2.18.0,<3.0.0dev" [package.extras] @@ -446,13 +446,13 @@ testing = ["pytest"] [[package]] name = "google-resumable-media" -version = "2.7.1" +version = "2.7.2" description = "Utilities for Google Media Downloads and Resumable Uploads" optional = false python-versions = ">=3.7" files = [ - {file = "google-resumable-media-2.7.1.tar.gz", hash = "sha256:eae451a7b2e2cdbaaa0fd2eb00cc8a1ee5e95e16b55597359cbc3d27d7d90e33"}, - {file = "google_resumable_media-2.7.1-py2.py3-none-any.whl", hash = "sha256:103ebc4ba331ab1bfdac0250f8033627a2cd7cde09e7ccff9181e31ba4315b2c"}, + {file = "google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa"}, + {file = "google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0"}, ] [package.dependencies] @@ -517,13 +517,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "8.2.0" +version = "8.4.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, ] [package.dependencies] @@ -1089,13 +1089,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "zipp" -version = "3.19.2" +version = "3.20.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, + {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, + {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, ] [package.extras] diff --git a/pyproject.toml b/pyproject.toml index 530ef3fe..44a23720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gen3cirrus" -version = "3.0.1" +version = "3.1.0" description = "" authors = ["Center for Translational Data Science at the University of Chicago "] license = "Apache-2.0" From 81219ad6ab64f69ca030dca28b08cc948c6091c9 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 21 Aug 2024 16:20:09 -0500 Subject: [PATCH 16/19] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06710f28..0e9e00d2 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ object = "test.txt" bucket = "testBucket" expiration = 3600 -url = aws.requester_pays_download_presigned_url( bucket, object, expiration) +url = aws.requester_pays_download_presigned_url(bucket, object, expiration) ``` ## Setting up Environment for `cirrus` From a85699faefcfe8e4c5d4f962af86370e1b7e150c Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Thu, 22 Aug 2024 11:51:24 -0500 Subject: [PATCH 17/19] update tests --- test/test_utils.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index 89374950..28e557ec 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -140,7 +140,7 @@ def test_aws_get_presigned_url(): url = generate_presigned_url(s3, "get", bucket, obj, expires) - assert url != None + assert url is not None def test_aws_get_presigned_url_requester_pays(): @@ -155,4 +155,21 @@ def test_aws_get_presigned_url_requester_pays(): url = generate_presigned_url_requester_pays(s3, bucket, obj, expires) - assert url != None + assert url is not None + + +def test_aws_get_presigned_url_with_invalid_method(): + """ + Test that we can not get a presigned url if the method is not valid + """ + + s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") + + bucket = "test" + obj = "test-obj.txt" + expires = 3600 + + url = generate_presigned_url( + s3, "something else than put or get", bucket, obj, expires + ) + assert url is None From e6fb45b79d30d37148d68785b45b436dd2e53fb3 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Thu, 22 Aug 2024 14:26:55 -0500 Subject: [PATCH 18/19] add param modification logic and tests --- gen3cirrus/aws/utils.py | 67 +++++++++++++++++++++++++++++++++++++++-- test/test_utils.py | 37 ++++++++++++++++++++++- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/gen3cirrus/aws/utils.py b/gen3cirrus/aws/utils.py index cdaf65b8..fe277ddd 100644 --- a/gen3cirrus/aws/utils.py +++ b/gen3cirrus/aws/utils.py @@ -1,9 +1,72 @@ +from urllib.parse import urlencode from botocore.exceptions import ClientError from cdislogging import get_logger logger = get_logger(__name__, log_level="info") +custom_params = ["user_id", "username", "client_id", "x-amz-request-payer"] + + +def is_custom_params(param_key): + """ + Little helper function for checking if a param key should be skipping from validation + + Args: + param_key (string): a key of a param + """ + if param_key in custom_params: + return True + else: + return False + + +def client_param_handler(*, params, context, **_kw): + """ + Little helper function for removing customized params before validating + + Args: + params (dict): a dict of parameters + context (context): for temporarily storing those removed parameters + """ + # Store custom parameters in context for later event handlers + context["custom_params"] = {k: v for k, v in params.items() if is_custom_params(k)} + # Remove custom parameters from client parameters, + # because validation would fail on them + return {k: v for k, v in params.items() if not is_custom_params(k)} + + +def request_param_injector(*, request, **_kw): + """ + Little helper function for adding customized params back into url before signing + + Args: + request (request): request for presigned url + """ + if request.context["custom_params"]: + request.url += "&" if "?" in request.url else "?" + request.url += urlencode(request.context["custom_params"]) + + +def customize_s3_client_param_events(s3_client): + """ + Function for modifying the params that need to be included when signing + This is needed because we need to include some customized params in the signed url, but boto3 won't allow them to exist out of the box + See https://stackoverflow.com/a/59057975 + + Args: + s3_client (S3.Client): boto3 S3 client + """ + s3_client.meta.events.register( + "provide-client-params.s3.GetObject", client_param_handler + ) + s3_client.meta.events.register("before-sign.s3.GetObject", request_param_injector) + s3_client.meta.events.register( + "provide-client-params.s3.PutObject", client_param_handler + ) + s3_client.meta.events.register("before-sign.s3.PutObject", request_param_injector) + return s3_client + def generate_presigned_url( client, method, bucket_name, object_name, expires, additional_info=None @@ -28,7 +91,7 @@ def generate_presigned_url( for key in additional_info: params[key] = additional_info[key] - s3_client = client + s3_client = customize_s3_client_param_events(client) if method == "get": client_method = "get_object" @@ -112,7 +175,7 @@ def generate_presigned_url_requester_pays( for key in additional_info: params[key] = additional_info[key] - s3_client = client + s3_client = customize_s3_client_param_events(client) try: response = s3_client.generate_presigned_url( diff --git a/test/test_utils.py b/test/test_utils.py index 28e557ec..4ea33271 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -2,6 +2,7 @@ import pytest from urllib.parse import quote +from botocore.exceptions import ParamValidationError from gen3cirrus.google_cloud.utils import ( _get_string_to_sign, @@ -143,6 +144,40 @@ def test_aws_get_presigned_url(): assert url is not None +def test_aws_get_presigned_url_with_valid_additional_info(): + """ + Test that we can get a presigned url from a bucket with some valid additional info + """ + + s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") + + bucket = "test" + obj = "test-obj.txt" + expires = 3600 + additional_info = {"user_id": "test_user_id", "username": "test_username"} + + url = generate_presigned_url(s3, "get", bucket, obj, expires, additional_info) + + assert url is not None + + +def test_aws_get_presigned_url_with_invalid_additional_info(): + """ + Test that we cannot get a presigned url from a bucket with invalid additional info + """ + + s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") + + bucket = "test" + obj = "test-obj.txt" + expires = 3600 + additional_info = {"some_random_key": "some_random_value"} + + with pytest.raises(ParamValidationError): + url = generate_presigned_url(s3, "get", bucket, obj, expires, additional_info) + assert url is None + + def test_aws_get_presigned_url_requester_pays(): """ Test that we can get a presigned url from a requester pays bucket @@ -160,7 +195,7 @@ def test_aws_get_presigned_url_requester_pays(): def test_aws_get_presigned_url_with_invalid_method(): """ - Test that we can not get a presigned url if the method is not valid + Test that we cannot get a presigned url if the method is not valid """ s3 = boto3.client("s3", aws_access_key_id="", aws_secret_access_key="") From 6aaf512f7ed15c1132cbd5f6be079e71778234ab Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Thu, 22 Aug 2024 14:41:21 -0500 Subject: [PATCH 19/19] fix --- gen3cirrus/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gen3cirrus/__init__.py b/gen3cirrus/__init__.py index 8ebde938..d2082cee 100644 --- a/gen3cirrus/__init__.py +++ b/gen3cirrus/__init__.py @@ -1,2 +1,3 @@ # Expose public API from each cloud provider from .google_cloud import GoogleCloudManager +from .aws import AwsService