diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..02ca207 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,196 @@ +name: Build 'n Deploy + +on: + push: + branches: + - '*' + tags-ignore: + - '*' + paths-ignore: + - 'pyproject.toml' + - 'bumpver.toml' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + inputs: + venue: + type: choice + description: Venue to deploy to + options: + - SIT + - UAT + - OPS + commit: + type: string + description: Custom commit hash + +jobs: + build: + name: build, lint, and test ingest-to-sds + runs-on: ubuntu-latest + outputs: + deploy_env: ${{ steps.set-env.outputs.deploy_env }} + github_sha: ${{ steps.update-sha.outputs.github_sha }} + steps: + # -- Setup -- + - uses: getsentry/action-github-app-token@v2 + name: my-app-install token + id: podaac-cicd + with: + app_id: ${{ secrets.CICD_APP_ID }} + private_key: ${{ secrets.CICD_APP_PRIVATE_KEY }} + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.3.7 + - name: Initial checkout ${{ github.ref }} + if: github.event.inputs.commit == '' + uses: actions/checkout@v4 + - name: Adjust to proper commit hash ${{ github.event.inputs.commit }} + if: github.event.inputs.commit != '' + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.commit }} + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install bumpver & poetry + run: pip3 install bumpver poetry poetry-plugin-bundle + - name: Setup git user + run: | + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + - name: Install dependencies + run: poetry install + + # -- Testing & Linting -- + - name: Lint + run: | + poetry run flake8 podaac/ tests/ + poetry run pylint podaac/ tests/ + - name: Test + run: poetry run pytest + - name: Validate Terraform + run: terraform validate -no-color + + # -- Version Bumping -- + - name: Manual execution means no version bump + # If triggered by workflow dispatch, no version bump + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + echo "TARGET_ENV=${{ github.event.inputs.venue }}" >> $GITHUB_ENV + + TARGET_ENV=${{ github.event.inputs.venue }} + - name: Bump alpha version + if: github.ref == 'refs/heads/develop' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' + run: | + TAG=$(bumpver show -e | awk -F= '$1 == "TAG" {print $2};') + if [ $TAG == 'final' ]; then + # Bump patch version first then append tag + bumpver update --patch --tag alpha --tag-num + else + bumpver update --tag alpha --tag-num + fi + echo "TARGET_ENV=SIT" >> $GITHUB_ENV + - name: Bump rc version + if: startsWith(github.ref, 'refs/heads/release/') && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' + run: | + bumpver update -f --tag rc --tag-num + echo "TARGET_ENV=UAT" >> $GITHUB_ENV + - name: Release version + if: github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' + run: | + bumpver update -f --tag final + echo "TARGET_ENV=OPS" >> $GITHUB_ENV + - name: Set the target environment to ${{ env.TARGET_ENV }} + id: set-env + run: | + echo "deploy_env=${{ env.TARGET_ENV }}" >> $GITHUB_OUTPUT + # -- Build -- + - name: Build lambda package + run: | + ./build.sh + - name: Upload packaged zip + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/*.zip + - name: Set github SHA for deployment + id: update-sha + run: | + SHA=$(git rev-parse HEAD) + echo "github_sha=${SHA}" >> $GITHUB_OUTPUT + + deploy: + name: Deploy + needs: build + # The type of runner that the job will run on + runs-on: ubuntu-latest + environment: + name: ${{ needs.build.outputs.deploy_env }} + if: | + (github.ref == 'refs/heads/develop' && github.event_name != 'pull_request') || + (github.ref == 'refs/heads/main' && github.event_name != 'pull_request') || + (startsWith(github.ref, 'refs/heads/release') && github.event_name != 'pull_request') || + github.event_name == 'workflow_dispatch' + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-west-2 + role-session-name: GitHubActions + aws-access-key-id: ${{ secrets[vars.AWS_ACCESS_KEY_ID_SECRET_NAME] }} + aws-secret-access-key: ${{ secrets[vars.AWS_SECRET_ACCESS_KEY_SECRET_NAME] }} + mask-aws-account-id: true + - uses: actions/checkout@v4 + with: + ref: ${{ needs.build.outputs.github_sha }} + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.TERRAFORM_VERSION }} + terraform_wrapper: false + - name: Retrieve artifact from build step + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - name: Deploy to ${{ needs.build.outputs.deploy_env }} + id: terraform-deploy + working-directory: terraform/ + env: + TF_VAR_edl_base_url: ${{ secrets.EDL_BASE_URL }} + TF_VAR_edl_client_id: ${{ secrets.EDL_CLIENT_ID }} + TF_VAR_edl_client_secret: ${{ secrets.EDL_CLIENT_SECRET }} + TF_VAR_session_encryption_key: ${{ secrets.SESSION_ENCRYPTION_KEY }} + TF_VAR_ingest_aws_account: ${{ secrets.INGEST_AWS_ACCOUNT }} + TF_VAR_ingest_aws_role: ${{ secrets.INGEST_AWS_ROLE }} + TF_VAR_sds_ca_cert_path: ${{ runner.temp }}/JPLICA.pem + TF_VAR_sds_host: ${{ secrets.SDS_HOST }} + TF_VAR_sds_username: ${{ secrets.SDS_USERNAME }} + TF_VAR_sds_password: ${{ secrets.SDS_PASSWORD }} + run: | + echo "${{ secrets.JPLICA_CERT }}" >> ${{ runner.temp }}/JPLICA.pem + source bin/config.sh ${{ vars.TF_VENUE }} + terraform apply -auto-approve + - name: Retrieve version number for notifications + run: | + VERSION=$(cat bumpver.toml|grep current_version |grep -v {version} |sed -E "s/current_version = //"|sed -E "s/\"//g") + echo "SUBMODULE_VERSION=$VERSION">>$GITHUB_ENV + - name: Send notifications to slack + uses: slackapi/slack-github-action@v1.25.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.NOTIFICATION_WEBHOOK_SWODLR }} + with: + payload: | + { + "message": "${{ github.repository }} [version ${{ env.SUBMODULE_VERSION }}] has been deployed to the ${{ needs.build.outputs.deploy_env }} environment" + } + - name: Send failure notifications to slack + if: failure() + uses: slackapi/slack-github-action@v1.25.0 + env: + SLACK_WEBHOOK_URL: ${{ secrets.NOTIFICATION_WEBHOOK_SWODLR }} + with: + payload: | + { + "message": "ERROR: ${{ github.repository }} [version ${{ env.SUBMODULE_VERSION }}] has encountered an error while trying to deploy to the ${{ needs.build.outputs.deploy_env }} environment" + } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e49a794 --- /dev/null +++ b/.gitignore @@ -0,0 +1,202 @@ +# macOS/IDE +.DS_Store +.vs_code/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# IDEs +.idea/ +.vscode/ +pyrightconfig.json + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +/.jplcert/ +/terraform/tfplan +/.aws/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..9df317e --- /dev/null +++ b/build.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eo pipefail + +PACKAGE_NAME=$(awk -F' = ' '{gsub(/"/,"");if($1=="name")print $2}' pyproject.toml) +VERSION=$(poetry version -s) + +ROOT_PATH="$PWD" +ZIP_PATH="$ROOT_PATH/dist/$PACKAGE_NAME-$VERSION.zip" + +# Install the bundle plugin using `poetry self add poetry-plugin-bundle` +poetry bundle venv build --clear --without=dev + +cd build/lib/python3.*/site-packages +touch podaac/__init__.py +rm -rf *.dist-info _virtualenv.* +find . -type d -name __pycache__ -exec rm -rf {} \+ + +mkdir -p "$ROOT_PATH/dist/" +rm -f "$ZIP_PATH" +zip -vr9 "$ZIP_PATH" . diff --git a/bumpver.toml b/bumpver.toml new file mode 100644 index 0000000..6947817 --- /dev/null +++ b/bumpver.toml @@ -0,0 +1,14 @@ +[bumpver] +current_version = "0.0.2-alpha14" +version_pattern = "MAJOR.MINOR.PATCH[-TAGNUM]" +commit = true +tag = true +push = true + +[bumpver.file_patterns] +"pyproject.toml" = [ + 'version = "{version}"' +] +"bumpver.toml" = [ + 'current_version = "{version}"', +] diff --git a/example.env b/example.env new file mode 100644 index 0000000..2388dc7 --- /dev/null +++ b/example.env @@ -0,0 +1,5 @@ +SWODLR_ENV=dev +SWODLR_sds_pcm_release_tag=1.0.0 +SWODLR_sds_host=http://sds.example/ +SWODLR_sds_username=swodlr_ingest +SWODLR_sds_password=password123 diff --git a/podaac/swodlr_ingest_to_sds/__init__.py b/podaac/swodlr_ingest_to_sds/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/podaac/swodlr_ingest_to_sds/__main__.py b/podaac/swodlr_ingest_to_sds/__main__.py new file mode 100644 index 0000000..2342f3c --- /dev/null +++ b/podaac/swodlr_ingest_to_sds/__main__.py @@ -0,0 +1,54 @@ +'''Command line tool for submitting an individual granule to the SDS''' +import logging +from argparse import ArgumentParser +import json +from pathlib import PurePath +from urllib.parse import urlsplit +import boto3 + +logging.basicConfig(level=logging.INFO) +sns = boto3.client('sns') + + +def main(): + ''' + Main entry point for the script + ''' + + parser = ArgumentParser() + + parser.add_argument('topic_arn') + parser.add_argument('s3_url') + + args = parser.parse_args() + + res = sns.publish( + TopicArn=args.topic_arn, + Message=json.dumps(_gen_cnm_r(args.s3_url)) + ) + logging.info('Sent SNS message; id: %s', res['MessageId']) + + +def _gen_cnm_r(s3_url): + url_components = urlsplit(s3_url) + path = PurePath(url_components.path) + + filename = path.name + granule_id = path.stem + + cmr_r = { + 'identifier': granule_id, + 'product': { + 'files': [{ + 'name': filename, + 'uri': s3_url, + 'type': 'data' + }] + } + } + + return cmr_r + + +if __name__ == '__main__': + main() diff --git a/podaac/swodlr_ingest_to_sds/bootstrap.py b/podaac/swodlr_ingest_to_sds/bootstrap.py new file mode 100644 index 0000000..8574439 --- /dev/null +++ b/podaac/swodlr_ingest_to_sds/bootstrap.py @@ -0,0 +1,19 @@ +'''Lambda to bootstrap step function execution''' +import json +import boto3 +from podaac.swodlr_ingest_to_sds.utilities import utils + +stepfunctions = boto3.client('stepfunctions') +ingest_sf_arn = utils.get_param('stepfunction_arn') +logger = utils.get_logger(__name__) + + +def lambda_handler(event, _context): + '''Starts step function execution''' + + sf_input = json.dumps(event, separators=(',', ':')) + result = stepfunctions.start_execution( + stateMachineArn=ingest_sf_arn, + input=sf_input + ) + logger.info('Started step function execution: %s', result['executionArn']) diff --git a/podaac/swodlr_ingest_to_sds/errors.py b/podaac/swodlr_ingest_to_sds/errors.py new file mode 100644 index 0000000..52e4248 --- /dev/null +++ b/podaac/swodlr_ingest_to_sds/errors.py @@ -0,0 +1,11 @@ +'''swodlr-ingest-to-sds specific errors''' + + +class DataNotFoundError(RuntimeError): + ''' + Thrown when no acceptable granule files are found in a CMN-R message + ''' + def __init__(self): + super().__init__( + 'Data file not found' + ) diff --git a/podaac/swodlr_ingest_to_sds/poll_status.py b/podaac/swodlr_ingest_to_sds/poll_status.py new file mode 100644 index 0000000..1debaf8 --- /dev/null +++ b/podaac/swodlr_ingest_to_sds/poll_status.py @@ -0,0 +1,103 @@ +'''Lambda to poll SDS for job status and update DynamoDB''' +from datetime import datetime +from copy import deepcopy +import re +from podaac.swodlr_common import sds_statuses +from podaac.swodlr_ingest_to_sds.utilities import utils + + +PRODUCT_REGEX = re.compile( + r'_(?PPIXC(Vec)?)_(?P\d{3})_(?P\d{3})_(?P\d{3})(?P(R|L))_' # pylint: disable=line-too-long # noqa: E501 +) + +logger = utils.get_logger(__name__) + + +def lambda_handler(event, _context): + ''' + Polls SDS for job status and updates DynamoDB. Returns the remaining jobs + that have not completed. + ''' + new_event = deepcopy(event) + + for item in event['jobs']: + granule_id = item['granule_id'] + job_id = item['job_id'] + + try: + job = utils.mozart_client.get_job_by_id(job_id) + info = job.get_info() + status = info['status'] + timestamp = datetime.now().isoformat() + logger.debug('granule id: %s; job id: %s; status: %s', + granule_id, job_id, status) + + update_expression = ( + 'SET #status = :status' + ',#last_check = :last_check' + ) + expression_attribute_names = { + '#status': 'status', + '#last_check': 'last_check' + } + expression_attribute_values = { + ':status': status, + ':last_check': timestamp + } + + if 'traceback' in info: + update_expression += ',#traceback = :traceback' + expression_attribute_names['#traceback'] = 'traceback' + expression_attribute_values[':traceback'] = info['traceback'] + + utils.ingest_table.update_item( + Key={'granule_id': granule_id}, + UpdateExpression=update_expression, + ExpressionAttributeNames=expression_attribute_names, + ExpressionAttributeValues=expression_attribute_values + ) + + if status in sds_statuses.FAIL: + logger.error('Job id: %s; status: %s', job_id, status) + if 'traceback' in info: + logger.error( + 'Job id: %s; traceback: %s', job_id, info['traceback'] + ) + + new_event['jobs'].remove(item) + elif status in sds_statuses.SUCCESS: + logger.info('Job id: %s; status: %s', job_id, status) + + # Insert into available tiles table + cpt = _extract_cpt(granule_id) + if cpt is None: + logger.error( + 'CPT not found: granule_id=%s, job_id=%s', + granule_id, job_id + ) + else: + tile_id = f'{cpt["product"]},{cpt["cycle"]},{cpt["pass"]},{cpt["tile"]}' # pylint: disable=line-too-long # noqa: E501 + utils.available_tiles_table.put_item( + Item={'tile_id': tile_id} + ) + + new_event['jobs'].remove(item) # Remove from queue + # Otello raises very generic exceptions + except Exception: # pylint: disable=broad-except + logger.exception('Failed to get status: %s', job_id) + + return new_event + + +def _extract_cpt(granule_id): + parsed_id = PRODUCT_REGEX.search(granule_id) + if parsed_id is None: + return None + + return { + 'product': parsed_id.group('product'), + 'cycle': str(int(parsed_id.group('cycle'))), + 'pass': str(int(parsed_id.group('pass'))), + 'tile': str(int(parsed_id.group('tile'))) + + parsed_id.group('direction') + } diff --git a/podaac/swodlr_ingest_to_sds/submit_to_sds.py b/podaac/swodlr_ingest_to_sds/submit_to_sds.py new file mode 100644 index 0000000..f135900 --- /dev/null +++ b/podaac/swodlr_ingest_to_sds/submit_to_sds.py @@ -0,0 +1,175 @@ +'''Lambda to submit granules to the SDS for ingestion''' +from datetime import datetime +import json +from pathlib import PurePath +from urllib.parse import urlsplit, urlunsplit +import boto3 +from podaac.swodlr_common import sds_statuses +from podaac.swodlr_ingest_to_sds.errors import DataNotFoundError +from podaac.swodlr_ingest_to_sds.utilities import utils + +ACCEPTED_EXTS = ['nc'] +INGEST_QUEUE_URL = utils.get_param('ingest_queue_url') +INGEST_TABLE_NAME = utils.get_param('ingest_table_name') +PCM_RELEASE_TAG = utils.get_param('sds_pcm_release_tag') + +dynamodb = boto3.client('dynamodb') +sqs = boto3.client('sqs') + +logger = utils.get_logger(__name__) +ingest_job_type = utils.mozart_client.get_job_type( + f'job-INGEST_STAGED:{PCM_RELEASE_TAG}' +) +ingest_job_type.initialize() + + +def lambda_handler(event, _context): + ''' + Lambda handler which submits granules to the SDS for ingestion if they are + not already ingested and inserts the granule and job info into DynamoDB + ''' + + logger.debug('Records received: %d', len(event['Records'])) + + granules = {} + for record in event['Records']: + try: + granule = _parse_record(record) + granules[granule['id']] = granule + except (DataNotFoundError, json.JSONDecodeError): + logger.exception('Failed to parse record') + + lookup_results = dynamodb.batch_get_item( + RequestItems={ + INGEST_TABLE_NAME: { + 'Keys': [{'granule_id': {'S': granule['id']}} + for granule in granules.values()], + 'ProjectionExpression': 'granule_id, #status', + 'ExpressionAttributeNames': {'#status': 'status'} + } + }, + ReturnConsumedCapacity='NONE' + ) + + for item in lookup_results['Responses'][INGEST_TABLE_NAME]: + granule_id = item['granule_id']['S'] + status = item['status']['S'] + if granule_id in granules and status in sds_statuses.SUCCESS: + logger.info('Granule already ingested: %s', granule_id) + del granules[granule_id] + + jobs = [] + with utils.ingest_table.batch_writer() as batch: + for granule in granules.values(): + try: + job = _ingest_granule(granule) + jobs.append({ + 'granule_id': granule['id'], + 'job_id': job['job_id'] + }) + + batch.put_item( + Item={ + 'granule_id': granule['id'], + 's3_url': granule['s3_url'], + 'job_id': job['job_id'], + 'status': job['status'], + 'last_check': job['timestamp'] + } + ) + # Otello throws generic Exceptions + except Exception: # pylint: disable=broad-exception-caught + logger.exception('Failed to ingest granule') + + return {'jobs': jobs} + + +def _parse_record(record): + cmr_r_message = json.loads(record['body']) + filename, s3_url = _extract_s3_url(cmr_r_message) + identifier = filename.split('.', 1)[0] + + return { + 'id': identifier, + 'filename': filename, + 's3_url': s3_url + } + + +def _ingest_granule(granule): + filename = granule['filename'] + s3_url = granule['s3_url'] + + logger.debug('Ingesting granule id: %s', granule['id']) + + job_params = _gen_mozart_job_params(filename, s3_url) + tag = f'ingest_file_otello__{filename}' + + ingest_job_type.set_input_params(job_params) + job = ingest_job_type.submit_job(tag=tag) + timestamp = datetime.now().isoformat() + logger.info('Submitted to sds: %s', granule['id']) + + return { + 'job_id': job.job_id, + 'status': 'job-queued', + 'timestamp': timestamp + } + + +def _extract_s3_url(cnm_r_message, strict=True): + files = cnm_r_message['product']['files'] + for file in files: + if _accept_file(file, strict): + url_elements = urlsplit(file['uri']) + if url_elements.scheme == 's3': + # Accept s3 urls as-is + return (file['name'], file['uri']) + + path_segments = url_elements.path[1:].split('/') + s3_elements = ( + 's3', # scheme + path_segments[0], # netloc + '/'.join(path_segments[1:]), # path + '', # query + '' # fragment + ) + + s3_url = urlunsplit(s3_elements) + logger.debug("S3 url: %s", s3_elements) + + return (file['name'], s3_url) + + logger.debug('Rejected file: %s', file['name']) + + if strict: + # Rerun without strict mode + return _extract_s3_url(cnm_r_message, False) + + raise DataNotFoundError() + + +def _accept_file(file, strict): + if strict: + ext = PurePath(file['name']).suffix[1:].lower() + return ext in ACCEPTED_EXTS + + return file['type'] == 'data' + + +def _gen_mozart_job_params(filename, url): + params = { + 'id': filename, + 'data_url': url, + 'data_file': filename, + 'prod_met': { + 'tags': ['ISL', url], + 'met_required': False, + 'restaged': False, + 'ISL_urls': url + }, + 'create_hash': 'false', # Why is this a string? + 'update_s3_tag': 'false' # Why is this a string? + } + + return params diff --git a/podaac/swodlr_ingest_to_sds/utilities.py b/podaac/swodlr_ingest_to_sds/utilities.py new file mode 100644 index 0000000..1eae3c3 --- /dev/null +++ b/podaac/swodlr_ingest_to_sds/utilities.py @@ -0,0 +1,64 @@ +'''Shared utilities for ingest-to-sds lambdas''' +import boto3 +from otello.mozart import Mozart + +from podaac.swodlr_common.utilities import BaseUtilities + + +class Utilities(BaseUtilities): + '''Utility functions implemented as a singleton''' + APP_NAME = 'swodlr' + SERVICE_NAME = 'ingest-to-sds' + + def __init__(self): + super().__init__(Utilities.APP_NAME, Utilities.SERVICE_NAME) + + @property + def mozart_client(self): + ''' + Lazily creates a Mozart client + ''' + if not hasattr(self, '_mozart_client'): + host = self.get_param('sds_host') + username = self.get_param('sds_username') + cfg = { + 'host': host, + 'auth': True, + 'username': username + } + + # pylint: disable=attribute-defined-outside-init + self._mozart_client = Mozart(cfg, session=self._get_sds_session()) + + return self._mozart_client + + @property + def ingest_table(self): + ''' + Lazily creates a DynamoDB table resource + ''' + if not hasattr(self, '_ingest_table'): + dynamodb = boto3.resource('dynamodb') + # pylint: disable=attribute-defined-outside-init + self._ingest_table = dynamodb.Table( + self.get_param('ingest_table_name') + ) + + return self._ingest_table + + @property + def available_tiles_table(self): + ''' + Lazily creates a DynamoDB table resource + ''' + if not hasattr(self, '_available_tiles_table'): + dynamodb = boto3.resource('dynamodb') + # pylint: disable=attribute-defined-outside-init + self._available_tiles_table = dynamodb.Table( + self.get_param('available_tiles_table_name') + ) + + return self._available_tiles_table + + +utils = Utilities() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..1c9c7cc --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1320 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] + +[[package]] +name = "boto3" +version = "1.33.3" +description = "The AWS SDK for Python" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.33.3-py3-none-any.whl", hash = "sha256:326b563021ed16470779df3bb372d55054f897bf2d72395bdf92693b3eeb4dd3"}, + {file = "boto3-1.33.3.tar.gz", hash = "sha256:8edc92b27a500728d55cf5d69d82ccece163491e274a7783c705010e58b1500f"}, +] + +[package.dependencies] +botocore = ">=1.33.3,<1.34.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.8.0,<0.9.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "boto3-stubs-lite" +version = "1.33.3" +description = "Type annotations for boto3 1.33.3 generated with mypy-boto3-builder 7.21.0" +optional = false +python-versions = ">=3.7" +files = [ + {file = "boto3-stubs-lite-1.33.3.tar.gz", hash = "sha256:403d413f80924115eb7257e80b33b2b28455ad8e1910e6a983a90f2d6dff1107"}, + {file = "boto3_stubs_lite-1.33.3-py3-none-any.whl", hash = "sha256:425408eb6386ab77f9fe355041f891e169510848bf9e98137d7b925fa8c46742"}, +] + +[package.dependencies] +botocore-stubs = "*" +mypy-boto3-dynamodb = {version = ">=1.33.0,<1.34.0", optional = true, markers = "extra == \"dynamodb\""} +types-s3transfer = "*" +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + +[package.extras] +accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.33.0,<1.34.0)"] +account = ["mypy-boto3-account (>=1.33.0,<1.34.0)"] +acm = ["mypy-boto3-acm (>=1.33.0,<1.34.0)"] +acm-pca = ["mypy-boto3-acm-pca (>=1.33.0,<1.34.0)"] +alexaforbusiness = ["mypy-boto3-alexaforbusiness (>=1.33.0,<1.34.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.33.0,<1.34.0)", "mypy-boto3-account (>=1.33.0,<1.34.0)", "mypy-boto3-acm (>=1.33.0,<1.34.0)", "mypy-boto3-acm-pca (>=1.33.0,<1.34.0)", "mypy-boto3-alexaforbusiness (>=1.33.0,<1.34.0)", "mypy-boto3-amp (>=1.33.0,<1.34.0)", "mypy-boto3-amplify (>=1.33.0,<1.34.0)", "mypy-boto3-amplifybackend (>=1.33.0,<1.34.0)", "mypy-boto3-amplifyuibuilder (>=1.33.0,<1.34.0)", "mypy-boto3-apigateway (>=1.33.0,<1.34.0)", "mypy-boto3-apigatewaymanagementapi (>=1.33.0,<1.34.0)", "mypy-boto3-apigatewayv2 (>=1.33.0,<1.34.0)", "mypy-boto3-appconfig (>=1.33.0,<1.34.0)", "mypy-boto3-appconfigdata (>=1.33.0,<1.34.0)", "mypy-boto3-appfabric (>=1.33.0,<1.34.0)", "mypy-boto3-appflow (>=1.33.0,<1.34.0)", "mypy-boto3-appintegrations (>=1.33.0,<1.34.0)", "mypy-boto3-application-autoscaling (>=1.33.0,<1.34.0)", "mypy-boto3-application-insights (>=1.33.0,<1.34.0)", "mypy-boto3-applicationcostprofiler (>=1.33.0,<1.34.0)", "mypy-boto3-appmesh (>=1.33.0,<1.34.0)", "mypy-boto3-apprunner (>=1.33.0,<1.34.0)", "mypy-boto3-appstream (>=1.33.0,<1.34.0)", "mypy-boto3-appsync (>=1.33.0,<1.34.0)", "mypy-boto3-arc-zonal-shift (>=1.33.0,<1.34.0)", "mypy-boto3-athena (>=1.33.0,<1.34.0)", "mypy-boto3-auditmanager (>=1.33.0,<1.34.0)", "mypy-boto3-autoscaling (>=1.33.0,<1.34.0)", "mypy-boto3-autoscaling-plans (>=1.33.0,<1.34.0)", "mypy-boto3-b2bi (>=1.33.0,<1.34.0)", "mypy-boto3-backup (>=1.33.0,<1.34.0)", "mypy-boto3-backup-gateway (>=1.33.0,<1.34.0)", "mypy-boto3-backupstorage (>=1.33.0,<1.34.0)", "mypy-boto3-batch (>=1.33.0,<1.34.0)", "mypy-boto3-bcm-data-exports (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock-agent (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock-agent-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-billingconductor (>=1.33.0,<1.34.0)", "mypy-boto3-braket (>=1.33.0,<1.34.0)", "mypy-boto3-budgets (>=1.33.0,<1.34.0)", "mypy-boto3-ce (>=1.33.0,<1.34.0)", "mypy-boto3-chime (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-identity (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-meetings (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-messaging (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-voice (>=1.33.0,<1.34.0)", "mypy-boto3-cleanrooms (>=1.33.0,<1.34.0)", "mypy-boto3-cleanroomsml (>=1.33.0,<1.34.0)", "mypy-boto3-cloud9 (>=1.33.0,<1.34.0)", "mypy-boto3-cloudcontrol (>=1.33.0,<1.34.0)", "mypy-boto3-clouddirectory (>=1.33.0,<1.34.0)", "mypy-boto3-cloudformation (>=1.33.0,<1.34.0)", "mypy-boto3-cloudfront (>=1.33.0,<1.34.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.33.0,<1.34.0)", "mypy-boto3-cloudhsm (>=1.33.0,<1.34.0)", "mypy-boto3-cloudhsmv2 (>=1.33.0,<1.34.0)", "mypy-boto3-cloudsearch (>=1.33.0,<1.34.0)", "mypy-boto3-cloudsearchdomain (>=1.33.0,<1.34.0)", "mypy-boto3-cloudtrail (>=1.33.0,<1.34.0)", "mypy-boto3-cloudtrail-data (>=1.33.0,<1.34.0)", "mypy-boto3-cloudwatch (>=1.33.0,<1.34.0)", "mypy-boto3-codeartifact (>=1.33.0,<1.34.0)", "mypy-boto3-codebuild (>=1.33.0,<1.34.0)", "mypy-boto3-codecatalyst (>=1.33.0,<1.34.0)", "mypy-boto3-codecommit (>=1.33.0,<1.34.0)", "mypy-boto3-codedeploy (>=1.33.0,<1.34.0)", "mypy-boto3-codeguru-reviewer (>=1.33.0,<1.34.0)", "mypy-boto3-codeguru-security (>=1.33.0,<1.34.0)", "mypy-boto3-codeguruprofiler (>=1.33.0,<1.34.0)", "mypy-boto3-codepipeline (>=1.33.0,<1.34.0)", "mypy-boto3-codestar (>=1.33.0,<1.34.0)", "mypy-boto3-codestar-connections (>=1.33.0,<1.34.0)", "mypy-boto3-codestar-notifications (>=1.33.0,<1.34.0)", "mypy-boto3-cognito-identity (>=1.33.0,<1.34.0)", "mypy-boto3-cognito-idp (>=1.33.0,<1.34.0)", "mypy-boto3-cognito-sync (>=1.33.0,<1.34.0)", "mypy-boto3-comprehend (>=1.33.0,<1.34.0)", "mypy-boto3-comprehendmedical (>=1.33.0,<1.34.0)", "mypy-boto3-compute-optimizer (>=1.33.0,<1.34.0)", "mypy-boto3-config (>=1.33.0,<1.34.0)", "mypy-boto3-connect (>=1.33.0,<1.34.0)", "mypy-boto3-connect-contact-lens (>=1.33.0,<1.34.0)", "mypy-boto3-connectcampaigns (>=1.33.0,<1.34.0)", "mypy-boto3-connectcases (>=1.33.0,<1.34.0)", "mypy-boto3-connectparticipant (>=1.33.0,<1.34.0)", "mypy-boto3-controltower (>=1.33.0,<1.34.0)", "mypy-boto3-cost-optimization-hub (>=1.33.0,<1.34.0)", "mypy-boto3-cur (>=1.33.0,<1.34.0)", "mypy-boto3-customer-profiles (>=1.33.0,<1.34.0)", "mypy-boto3-databrew (>=1.33.0,<1.34.0)", "mypy-boto3-dataexchange (>=1.33.0,<1.34.0)", "mypy-boto3-datapipeline (>=1.33.0,<1.34.0)", "mypy-boto3-datasync (>=1.33.0,<1.34.0)", "mypy-boto3-datazone (>=1.33.0,<1.34.0)", "mypy-boto3-dax (>=1.33.0,<1.34.0)", "mypy-boto3-detective (>=1.33.0,<1.34.0)", "mypy-boto3-devicefarm (>=1.33.0,<1.34.0)", "mypy-boto3-devops-guru (>=1.33.0,<1.34.0)", "mypy-boto3-directconnect (>=1.33.0,<1.34.0)", "mypy-boto3-discovery (>=1.33.0,<1.34.0)", "mypy-boto3-dlm (>=1.33.0,<1.34.0)", "mypy-boto3-dms (>=1.33.0,<1.34.0)", "mypy-boto3-docdb (>=1.33.0,<1.34.0)", "mypy-boto3-docdb-elastic (>=1.33.0,<1.34.0)", "mypy-boto3-drs (>=1.33.0,<1.34.0)", "mypy-boto3-ds (>=1.33.0,<1.34.0)", "mypy-boto3-dynamodb (>=1.33.0,<1.34.0)", "mypy-boto3-dynamodbstreams (>=1.33.0,<1.34.0)", "mypy-boto3-ebs (>=1.33.0,<1.34.0)", "mypy-boto3-ec2 (>=1.33.0,<1.34.0)", "mypy-boto3-ec2-instance-connect (>=1.33.0,<1.34.0)", "mypy-boto3-ecr (>=1.33.0,<1.34.0)", "mypy-boto3-ecr-public (>=1.33.0,<1.34.0)", "mypy-boto3-ecs (>=1.33.0,<1.34.0)", "mypy-boto3-efs (>=1.33.0,<1.34.0)", "mypy-boto3-eks (>=1.33.0,<1.34.0)", "mypy-boto3-eks-auth (>=1.33.0,<1.34.0)", "mypy-boto3-elastic-inference (>=1.33.0,<1.34.0)", "mypy-boto3-elasticache (>=1.33.0,<1.34.0)", "mypy-boto3-elasticbeanstalk (>=1.33.0,<1.34.0)", "mypy-boto3-elastictranscoder (>=1.33.0,<1.34.0)", "mypy-boto3-elb (>=1.33.0,<1.34.0)", "mypy-boto3-elbv2 (>=1.33.0,<1.34.0)", "mypy-boto3-emr (>=1.33.0,<1.34.0)", "mypy-boto3-emr-containers (>=1.33.0,<1.34.0)", "mypy-boto3-emr-serverless (>=1.33.0,<1.34.0)", "mypy-boto3-entityresolution (>=1.33.0,<1.34.0)", "mypy-boto3-es (>=1.33.0,<1.34.0)", "mypy-boto3-events (>=1.33.0,<1.34.0)", "mypy-boto3-evidently (>=1.33.0,<1.34.0)", "mypy-boto3-finspace (>=1.33.0,<1.34.0)", "mypy-boto3-finspace-data (>=1.33.0,<1.34.0)", "mypy-boto3-firehose (>=1.33.0,<1.34.0)", "mypy-boto3-fis (>=1.33.0,<1.34.0)", "mypy-boto3-fms (>=1.33.0,<1.34.0)", "mypy-boto3-forecast (>=1.33.0,<1.34.0)", "mypy-boto3-forecastquery (>=1.33.0,<1.34.0)", "mypy-boto3-frauddetector (>=1.33.0,<1.34.0)", "mypy-boto3-freetier (>=1.33.0,<1.34.0)", "mypy-boto3-fsx (>=1.33.0,<1.34.0)", "mypy-boto3-gamelift (>=1.33.0,<1.34.0)", "mypy-boto3-glacier (>=1.33.0,<1.34.0)", "mypy-boto3-globalaccelerator (>=1.33.0,<1.34.0)", "mypy-boto3-glue (>=1.33.0,<1.34.0)", "mypy-boto3-grafana (>=1.33.0,<1.34.0)", "mypy-boto3-greengrass (>=1.33.0,<1.34.0)", "mypy-boto3-greengrassv2 (>=1.33.0,<1.34.0)", "mypy-boto3-groundstation (>=1.33.0,<1.34.0)", "mypy-boto3-guardduty (>=1.33.0,<1.34.0)", "mypy-boto3-health (>=1.33.0,<1.34.0)", "mypy-boto3-healthlake (>=1.33.0,<1.34.0)", "mypy-boto3-honeycode (>=1.33.0,<1.34.0)", "mypy-boto3-iam (>=1.33.0,<1.34.0)", "mypy-boto3-identitystore (>=1.33.0,<1.34.0)", "mypy-boto3-imagebuilder (>=1.33.0,<1.34.0)", "mypy-boto3-importexport (>=1.33.0,<1.34.0)", "mypy-boto3-inspector (>=1.33.0,<1.34.0)", "mypy-boto3-inspector-scan (>=1.33.0,<1.34.0)", "mypy-boto3-inspector2 (>=1.33.0,<1.34.0)", "mypy-boto3-internetmonitor (>=1.33.0,<1.34.0)", "mypy-boto3-iot (>=1.33.0,<1.34.0)", "mypy-boto3-iot-data (>=1.33.0,<1.34.0)", "mypy-boto3-iot-jobs-data (>=1.33.0,<1.34.0)", "mypy-boto3-iot-roborunner (>=1.33.0,<1.34.0)", "mypy-boto3-iot1click-devices (>=1.33.0,<1.34.0)", "mypy-boto3-iot1click-projects (>=1.33.0,<1.34.0)", "mypy-boto3-iotanalytics (>=1.33.0,<1.34.0)", "mypy-boto3-iotdeviceadvisor (>=1.33.0,<1.34.0)", "mypy-boto3-iotevents (>=1.33.0,<1.34.0)", "mypy-boto3-iotevents-data (>=1.33.0,<1.34.0)", "mypy-boto3-iotfleethub (>=1.33.0,<1.34.0)", "mypy-boto3-iotfleetwise (>=1.33.0,<1.34.0)", "mypy-boto3-iotsecuretunneling (>=1.33.0,<1.34.0)", "mypy-boto3-iotsitewise (>=1.33.0,<1.34.0)", "mypy-boto3-iotthingsgraph (>=1.33.0,<1.34.0)", "mypy-boto3-iottwinmaker (>=1.33.0,<1.34.0)", "mypy-boto3-iotwireless (>=1.33.0,<1.34.0)", "mypy-boto3-ivs (>=1.33.0,<1.34.0)", "mypy-boto3-ivs-realtime (>=1.33.0,<1.34.0)", "mypy-boto3-ivschat (>=1.33.0,<1.34.0)", "mypy-boto3-kafka (>=1.33.0,<1.34.0)", "mypy-boto3-kafkaconnect (>=1.33.0,<1.34.0)", "mypy-boto3-kendra (>=1.33.0,<1.34.0)", "mypy-boto3-kendra-ranking (>=1.33.0,<1.34.0)", "mypy-boto3-keyspaces (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-archived-media (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-media (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-signaling (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.33.0,<1.34.0)", "mypy-boto3-kinesisanalytics (>=1.33.0,<1.34.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.33.0,<1.34.0)", "mypy-boto3-kinesisvideo (>=1.33.0,<1.34.0)", "mypy-boto3-kms (>=1.33.0,<1.34.0)", "mypy-boto3-lakeformation (>=1.33.0,<1.34.0)", "mypy-boto3-lambda (>=1.33.0,<1.34.0)", "mypy-boto3-launch-wizard (>=1.33.0,<1.34.0)", "mypy-boto3-lex-models (>=1.33.0,<1.34.0)", "mypy-boto3-lex-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-lexv2-models (>=1.33.0,<1.34.0)", "mypy-boto3-lexv2-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-license-manager (>=1.33.0,<1.34.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.33.0,<1.34.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.33.0,<1.34.0)", "mypy-boto3-lightsail (>=1.33.0,<1.34.0)", "mypy-boto3-location (>=1.33.0,<1.34.0)", "mypy-boto3-logs (>=1.33.0,<1.34.0)", "mypy-boto3-lookoutequipment (>=1.33.0,<1.34.0)", "mypy-boto3-lookoutmetrics (>=1.33.0,<1.34.0)", "mypy-boto3-lookoutvision (>=1.33.0,<1.34.0)", "mypy-boto3-m2 (>=1.33.0,<1.34.0)", "mypy-boto3-machinelearning (>=1.33.0,<1.34.0)", "mypy-boto3-macie2 (>=1.33.0,<1.34.0)", "mypy-boto3-managedblockchain (>=1.33.0,<1.34.0)", "mypy-boto3-managedblockchain-query (>=1.33.0,<1.34.0)", "mypy-boto3-marketplace-catalog (>=1.33.0,<1.34.0)", "mypy-boto3-marketplace-entitlement (>=1.33.0,<1.34.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.33.0,<1.34.0)", "mypy-boto3-mediaconnect (>=1.33.0,<1.34.0)", "mypy-boto3-mediaconvert (>=1.33.0,<1.34.0)", "mypy-boto3-medialive (>=1.33.0,<1.34.0)", "mypy-boto3-mediapackage (>=1.33.0,<1.34.0)", "mypy-boto3-mediapackage-vod (>=1.33.0,<1.34.0)", "mypy-boto3-mediapackagev2 (>=1.33.0,<1.34.0)", "mypy-boto3-mediastore (>=1.33.0,<1.34.0)", "mypy-boto3-mediastore-data (>=1.33.0,<1.34.0)", "mypy-boto3-mediatailor (>=1.33.0,<1.34.0)", "mypy-boto3-medical-imaging (>=1.33.0,<1.34.0)", "mypy-boto3-memorydb (>=1.33.0,<1.34.0)", "mypy-boto3-meteringmarketplace (>=1.33.0,<1.34.0)", "mypy-boto3-mgh (>=1.33.0,<1.34.0)", "mypy-boto3-mgn (>=1.33.0,<1.34.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.33.0,<1.34.0)", "mypy-boto3-migrationhub-config (>=1.33.0,<1.34.0)", "mypy-boto3-migrationhuborchestrator (>=1.33.0,<1.34.0)", "mypy-boto3-migrationhubstrategy (>=1.33.0,<1.34.0)", "mypy-boto3-mobile (>=1.33.0,<1.34.0)", "mypy-boto3-mq (>=1.33.0,<1.34.0)", "mypy-boto3-mturk (>=1.33.0,<1.34.0)", "mypy-boto3-mwaa (>=1.33.0,<1.34.0)", "mypy-boto3-neptune (>=1.33.0,<1.34.0)", "mypy-boto3-neptunedata (>=1.33.0,<1.34.0)", "mypy-boto3-network-firewall (>=1.33.0,<1.34.0)", "mypy-boto3-networkmanager (>=1.33.0,<1.34.0)", "mypy-boto3-nimble (>=1.33.0,<1.34.0)", "mypy-boto3-oam (>=1.33.0,<1.34.0)", "mypy-boto3-omics (>=1.33.0,<1.34.0)", "mypy-boto3-opensearch (>=1.33.0,<1.34.0)", "mypy-boto3-opensearchserverless (>=1.33.0,<1.34.0)", "mypy-boto3-opsworks (>=1.33.0,<1.34.0)", "mypy-boto3-opsworkscm (>=1.33.0,<1.34.0)", "mypy-boto3-organizations (>=1.33.0,<1.34.0)", "mypy-boto3-osis (>=1.33.0,<1.34.0)", "mypy-boto3-outposts (>=1.33.0,<1.34.0)", "mypy-boto3-panorama (>=1.33.0,<1.34.0)", "mypy-boto3-payment-cryptography (>=1.33.0,<1.34.0)", "mypy-boto3-payment-cryptography-data (>=1.33.0,<1.34.0)", "mypy-boto3-pca-connector-ad (>=1.33.0,<1.34.0)", "mypy-boto3-personalize (>=1.33.0,<1.34.0)", "mypy-boto3-personalize-events (>=1.33.0,<1.34.0)", "mypy-boto3-personalize-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-pi (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint-email (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint-sms-voice (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.33.0,<1.34.0)", "mypy-boto3-pipes (>=1.33.0,<1.34.0)", "mypy-boto3-polly (>=1.33.0,<1.34.0)", "mypy-boto3-pricing (>=1.33.0,<1.34.0)", "mypy-boto3-privatenetworks (>=1.33.0,<1.34.0)", "mypy-boto3-proton (>=1.33.0,<1.34.0)", "mypy-boto3-qbusiness (>=1.33.0,<1.34.0)", "mypy-boto3-qconnect (>=1.33.0,<1.34.0)", "mypy-boto3-qldb (>=1.33.0,<1.34.0)", "mypy-boto3-qldb-session (>=1.33.0,<1.34.0)", "mypy-boto3-quicksight (>=1.33.0,<1.34.0)", "mypy-boto3-ram (>=1.33.0,<1.34.0)", "mypy-boto3-rbin (>=1.33.0,<1.34.0)", "mypy-boto3-rds (>=1.33.0,<1.34.0)", "mypy-boto3-rds-data (>=1.33.0,<1.34.0)", "mypy-boto3-redshift (>=1.33.0,<1.34.0)", "mypy-boto3-redshift-data (>=1.33.0,<1.34.0)", "mypy-boto3-redshift-serverless (>=1.33.0,<1.34.0)", "mypy-boto3-rekognition (>=1.33.0,<1.34.0)", "mypy-boto3-repostspace (>=1.33.0,<1.34.0)", "mypy-boto3-resiliencehub (>=1.33.0,<1.34.0)", "mypy-boto3-resource-explorer-2 (>=1.33.0,<1.34.0)", "mypy-boto3-resource-groups (>=1.33.0,<1.34.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.33.0,<1.34.0)", "mypy-boto3-robomaker (>=1.33.0,<1.34.0)", "mypy-boto3-rolesanywhere (>=1.33.0,<1.34.0)", "mypy-boto3-route53 (>=1.33.0,<1.34.0)", "mypy-boto3-route53-recovery-cluster (>=1.33.0,<1.34.0)", "mypy-boto3-route53-recovery-control-config (>=1.33.0,<1.34.0)", "mypy-boto3-route53-recovery-readiness (>=1.33.0,<1.34.0)", "mypy-boto3-route53domains (>=1.33.0,<1.34.0)", "mypy-boto3-route53resolver (>=1.33.0,<1.34.0)", "mypy-boto3-rum (>=1.33.0,<1.34.0)", "mypy-boto3-s3 (>=1.33.0,<1.34.0)", "mypy-boto3-s3control (>=1.33.0,<1.34.0)", "mypy-boto3-s3outposts (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-edge (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-geospatial (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-metrics (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-savingsplans (>=1.33.0,<1.34.0)", "mypy-boto3-scheduler (>=1.33.0,<1.34.0)", "mypy-boto3-schemas (>=1.33.0,<1.34.0)", "mypy-boto3-sdb (>=1.33.0,<1.34.0)", "mypy-boto3-secretsmanager (>=1.33.0,<1.34.0)", "mypy-boto3-securityhub (>=1.33.0,<1.34.0)", "mypy-boto3-securitylake (>=1.33.0,<1.34.0)", "mypy-boto3-serverlessrepo (>=1.33.0,<1.34.0)", "mypy-boto3-service-quotas (>=1.33.0,<1.34.0)", "mypy-boto3-servicecatalog (>=1.33.0,<1.34.0)", "mypy-boto3-servicecatalog-appregistry (>=1.33.0,<1.34.0)", "mypy-boto3-servicediscovery (>=1.33.0,<1.34.0)", "mypy-boto3-ses (>=1.33.0,<1.34.0)", "mypy-boto3-sesv2 (>=1.33.0,<1.34.0)", "mypy-boto3-shield (>=1.33.0,<1.34.0)", "mypy-boto3-signer (>=1.33.0,<1.34.0)", "mypy-boto3-simspaceweaver (>=1.33.0,<1.34.0)", "mypy-boto3-sms (>=1.33.0,<1.34.0)", "mypy-boto3-sms-voice (>=1.33.0,<1.34.0)", "mypy-boto3-snow-device-management (>=1.33.0,<1.34.0)", "mypy-boto3-snowball (>=1.33.0,<1.34.0)", "mypy-boto3-sns (>=1.33.0,<1.34.0)", "mypy-boto3-sqs (>=1.33.0,<1.34.0)", "mypy-boto3-ssm (>=1.33.0,<1.34.0)", "mypy-boto3-ssm-contacts (>=1.33.0,<1.34.0)", "mypy-boto3-ssm-incidents (>=1.33.0,<1.34.0)", "mypy-boto3-ssm-sap (>=1.33.0,<1.34.0)", "mypy-boto3-sso (>=1.33.0,<1.34.0)", "mypy-boto3-sso-admin (>=1.33.0,<1.34.0)", "mypy-boto3-sso-oidc (>=1.33.0,<1.34.0)", "mypy-boto3-stepfunctions (>=1.33.0,<1.34.0)", "mypy-boto3-storagegateway (>=1.33.0,<1.34.0)", "mypy-boto3-sts (>=1.33.0,<1.34.0)", "mypy-boto3-support (>=1.33.0,<1.34.0)", "mypy-boto3-support-app (>=1.33.0,<1.34.0)", "mypy-boto3-swf (>=1.33.0,<1.34.0)", "mypy-boto3-synthetics (>=1.33.0,<1.34.0)", "mypy-boto3-textract (>=1.33.0,<1.34.0)", "mypy-boto3-timestream-query (>=1.33.0,<1.34.0)", "mypy-boto3-timestream-write (>=1.33.0,<1.34.0)", "mypy-boto3-tnb (>=1.33.0,<1.34.0)", "mypy-boto3-transcribe (>=1.33.0,<1.34.0)", "mypy-boto3-transfer (>=1.33.0,<1.34.0)", "mypy-boto3-translate (>=1.33.0,<1.34.0)", "mypy-boto3-trustedadvisor (>=1.33.0,<1.34.0)", "mypy-boto3-verifiedpermissions (>=1.33.0,<1.34.0)", "mypy-boto3-voice-id (>=1.33.0,<1.34.0)", "mypy-boto3-vpc-lattice (>=1.33.0,<1.34.0)", "mypy-boto3-waf (>=1.33.0,<1.34.0)", "mypy-boto3-waf-regional (>=1.33.0,<1.34.0)", "mypy-boto3-wafv2 (>=1.33.0,<1.34.0)", "mypy-boto3-wellarchitected (>=1.33.0,<1.34.0)", "mypy-boto3-wisdom (>=1.33.0,<1.34.0)", "mypy-boto3-workdocs (>=1.33.0,<1.34.0)", "mypy-boto3-worklink (>=1.33.0,<1.34.0)", "mypy-boto3-workmail (>=1.33.0,<1.34.0)", "mypy-boto3-workmailmessageflow (>=1.33.0,<1.34.0)", "mypy-boto3-workspaces (>=1.33.0,<1.34.0)", "mypy-boto3-workspaces-thin-client (>=1.33.0,<1.34.0)", "mypy-boto3-workspaces-web (>=1.33.0,<1.34.0)", "mypy-boto3-xray (>=1.33.0,<1.34.0)"] +amp = ["mypy-boto3-amp (>=1.33.0,<1.34.0)"] +amplify = ["mypy-boto3-amplify (>=1.33.0,<1.34.0)"] +amplifybackend = ["mypy-boto3-amplifybackend (>=1.33.0,<1.34.0)"] +amplifyuibuilder = ["mypy-boto3-amplifyuibuilder (>=1.33.0,<1.34.0)"] +apigateway = ["mypy-boto3-apigateway (>=1.33.0,<1.34.0)"] +apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (>=1.33.0,<1.34.0)"] +apigatewayv2 = ["mypy-boto3-apigatewayv2 (>=1.33.0,<1.34.0)"] +appconfig = ["mypy-boto3-appconfig (>=1.33.0,<1.34.0)"] +appconfigdata = ["mypy-boto3-appconfigdata (>=1.33.0,<1.34.0)"] +appfabric = ["mypy-boto3-appfabric (>=1.33.0,<1.34.0)"] +appflow = ["mypy-boto3-appflow (>=1.33.0,<1.34.0)"] +appintegrations = ["mypy-boto3-appintegrations (>=1.33.0,<1.34.0)"] +application-autoscaling = ["mypy-boto3-application-autoscaling (>=1.33.0,<1.34.0)"] +application-insights = ["mypy-boto3-application-insights (>=1.33.0,<1.34.0)"] +applicationcostprofiler = ["mypy-boto3-applicationcostprofiler (>=1.33.0,<1.34.0)"] +appmesh = ["mypy-boto3-appmesh (>=1.33.0,<1.34.0)"] +apprunner = ["mypy-boto3-apprunner (>=1.33.0,<1.34.0)"] +appstream = ["mypy-boto3-appstream (>=1.33.0,<1.34.0)"] +appsync = ["mypy-boto3-appsync (>=1.33.0,<1.34.0)"] +arc-zonal-shift = ["mypy-boto3-arc-zonal-shift (>=1.33.0,<1.34.0)"] +athena = ["mypy-boto3-athena (>=1.33.0,<1.34.0)"] +auditmanager = ["mypy-boto3-auditmanager (>=1.33.0,<1.34.0)"] +autoscaling = ["mypy-boto3-autoscaling (>=1.33.0,<1.34.0)"] +autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.33.0,<1.34.0)"] +b2bi = ["mypy-boto3-b2bi (>=1.33.0,<1.34.0)"] +backup = ["mypy-boto3-backup (>=1.33.0,<1.34.0)"] +backup-gateway = ["mypy-boto3-backup-gateway (>=1.33.0,<1.34.0)"] +backupstorage = ["mypy-boto3-backupstorage (>=1.33.0,<1.34.0)"] +batch = ["mypy-boto3-batch (>=1.33.0,<1.34.0)"] +bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.33.0,<1.34.0)"] +bedrock = ["mypy-boto3-bedrock (>=1.33.0,<1.34.0)"] +bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.33.0,<1.34.0)"] +bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.33.0,<1.34.0)"] +bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.33.0,<1.34.0)"] +billingconductor = ["mypy-boto3-billingconductor (>=1.33.0,<1.34.0)"] +boto3 = ["boto3 (==1.33.3)", "botocore (==1.33.3)"] +braket = ["mypy-boto3-braket (>=1.33.0,<1.34.0)"] +budgets = ["mypy-boto3-budgets (>=1.33.0,<1.34.0)"] +ce = ["mypy-boto3-ce (>=1.33.0,<1.34.0)"] +chime = ["mypy-boto3-chime (>=1.33.0,<1.34.0)"] +chime-sdk-identity = ["mypy-boto3-chime-sdk-identity (>=1.33.0,<1.34.0)"] +chime-sdk-media-pipelines = ["mypy-boto3-chime-sdk-media-pipelines (>=1.33.0,<1.34.0)"] +chime-sdk-meetings = ["mypy-boto3-chime-sdk-meetings (>=1.33.0,<1.34.0)"] +chime-sdk-messaging = ["mypy-boto3-chime-sdk-messaging (>=1.33.0,<1.34.0)"] +chime-sdk-voice = ["mypy-boto3-chime-sdk-voice (>=1.33.0,<1.34.0)"] +cleanrooms = ["mypy-boto3-cleanrooms (>=1.33.0,<1.34.0)"] +cleanroomsml = ["mypy-boto3-cleanroomsml (>=1.33.0,<1.34.0)"] +cloud9 = ["mypy-boto3-cloud9 (>=1.33.0,<1.34.0)"] +cloudcontrol = ["mypy-boto3-cloudcontrol (>=1.33.0,<1.34.0)"] +clouddirectory = ["mypy-boto3-clouddirectory (>=1.33.0,<1.34.0)"] +cloudformation = ["mypy-boto3-cloudformation (>=1.33.0,<1.34.0)"] +cloudfront = ["mypy-boto3-cloudfront (>=1.33.0,<1.34.0)"] +cloudfront-keyvaluestore = ["mypy-boto3-cloudfront-keyvaluestore (>=1.33.0,<1.34.0)"] +cloudhsm = ["mypy-boto3-cloudhsm (>=1.33.0,<1.34.0)"] +cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (>=1.33.0,<1.34.0)"] +cloudsearch = ["mypy-boto3-cloudsearch (>=1.33.0,<1.34.0)"] +cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (>=1.33.0,<1.34.0)"] +cloudtrail = ["mypy-boto3-cloudtrail (>=1.33.0,<1.34.0)"] +cloudtrail-data = ["mypy-boto3-cloudtrail-data (>=1.33.0,<1.34.0)"] +cloudwatch = ["mypy-boto3-cloudwatch (>=1.33.0,<1.34.0)"] +codeartifact = ["mypy-boto3-codeartifact (>=1.33.0,<1.34.0)"] +codebuild = ["mypy-boto3-codebuild (>=1.33.0,<1.34.0)"] +codecatalyst = ["mypy-boto3-codecatalyst (>=1.33.0,<1.34.0)"] +codecommit = ["mypy-boto3-codecommit (>=1.33.0,<1.34.0)"] +codedeploy = ["mypy-boto3-codedeploy (>=1.33.0,<1.34.0)"] +codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (>=1.33.0,<1.34.0)"] +codeguru-security = ["mypy-boto3-codeguru-security (>=1.33.0,<1.34.0)"] +codeguruprofiler = ["mypy-boto3-codeguruprofiler (>=1.33.0,<1.34.0)"] +codepipeline = ["mypy-boto3-codepipeline (>=1.33.0,<1.34.0)"] +codestar = ["mypy-boto3-codestar (>=1.33.0,<1.34.0)"] +codestar-connections = ["mypy-boto3-codestar-connections (>=1.33.0,<1.34.0)"] +codestar-notifications = ["mypy-boto3-codestar-notifications (>=1.33.0,<1.34.0)"] +cognito-identity = ["mypy-boto3-cognito-identity (>=1.33.0,<1.34.0)"] +cognito-idp = ["mypy-boto3-cognito-idp (>=1.33.0,<1.34.0)"] +cognito-sync = ["mypy-boto3-cognito-sync (>=1.33.0,<1.34.0)"] +comprehend = ["mypy-boto3-comprehend (>=1.33.0,<1.34.0)"] +comprehendmedical = ["mypy-boto3-comprehendmedical (>=1.33.0,<1.34.0)"] +compute-optimizer = ["mypy-boto3-compute-optimizer (>=1.33.0,<1.34.0)"] +config = ["mypy-boto3-config (>=1.33.0,<1.34.0)"] +connect = ["mypy-boto3-connect (>=1.33.0,<1.34.0)"] +connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.33.0,<1.34.0)"] +connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.33.0,<1.34.0)"] +connectcases = ["mypy-boto3-connectcases (>=1.33.0,<1.34.0)"] +connectparticipant = ["mypy-boto3-connectparticipant (>=1.33.0,<1.34.0)"] +controltower = ["mypy-boto3-controltower (>=1.33.0,<1.34.0)"] +cost-optimization-hub = ["mypy-boto3-cost-optimization-hub (>=1.33.0,<1.34.0)"] +cur = ["mypy-boto3-cur (>=1.33.0,<1.34.0)"] +customer-profiles = ["mypy-boto3-customer-profiles (>=1.33.0,<1.34.0)"] +databrew = ["mypy-boto3-databrew (>=1.33.0,<1.34.0)"] +dataexchange = ["mypy-boto3-dataexchange (>=1.33.0,<1.34.0)"] +datapipeline = ["mypy-boto3-datapipeline (>=1.33.0,<1.34.0)"] +datasync = ["mypy-boto3-datasync (>=1.33.0,<1.34.0)"] +datazone = ["mypy-boto3-datazone (>=1.33.0,<1.34.0)"] +dax = ["mypy-boto3-dax (>=1.33.0,<1.34.0)"] +detective = ["mypy-boto3-detective (>=1.33.0,<1.34.0)"] +devicefarm = ["mypy-boto3-devicefarm (>=1.33.0,<1.34.0)"] +devops-guru = ["mypy-boto3-devops-guru (>=1.33.0,<1.34.0)"] +directconnect = ["mypy-boto3-directconnect (>=1.33.0,<1.34.0)"] +discovery = ["mypy-boto3-discovery (>=1.33.0,<1.34.0)"] +dlm = ["mypy-boto3-dlm (>=1.33.0,<1.34.0)"] +dms = ["mypy-boto3-dms (>=1.33.0,<1.34.0)"] +docdb = ["mypy-boto3-docdb (>=1.33.0,<1.34.0)"] +docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.33.0,<1.34.0)"] +drs = ["mypy-boto3-drs (>=1.33.0,<1.34.0)"] +ds = ["mypy-boto3-ds (>=1.33.0,<1.34.0)"] +dynamodb = ["mypy-boto3-dynamodb (>=1.33.0,<1.34.0)"] +dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.33.0,<1.34.0)"] +ebs = ["mypy-boto3-ebs (>=1.33.0,<1.34.0)"] +ec2 = ["mypy-boto3-ec2 (>=1.33.0,<1.34.0)"] +ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (>=1.33.0,<1.34.0)"] +ecr = ["mypy-boto3-ecr (>=1.33.0,<1.34.0)"] +ecr-public = ["mypy-boto3-ecr-public (>=1.33.0,<1.34.0)"] +ecs = ["mypy-boto3-ecs (>=1.33.0,<1.34.0)"] +efs = ["mypy-boto3-efs (>=1.33.0,<1.34.0)"] +eks = ["mypy-boto3-eks (>=1.33.0,<1.34.0)"] +eks-auth = ["mypy-boto3-eks-auth (>=1.33.0,<1.34.0)"] +elastic-inference = ["mypy-boto3-elastic-inference (>=1.33.0,<1.34.0)"] +elasticache = ["mypy-boto3-elasticache (>=1.33.0,<1.34.0)"] +elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (>=1.33.0,<1.34.0)"] +elastictranscoder = ["mypy-boto3-elastictranscoder (>=1.33.0,<1.34.0)"] +elb = ["mypy-boto3-elb (>=1.33.0,<1.34.0)"] +elbv2 = ["mypy-boto3-elbv2 (>=1.33.0,<1.34.0)"] +emr = ["mypy-boto3-emr (>=1.33.0,<1.34.0)"] +emr-containers = ["mypy-boto3-emr-containers (>=1.33.0,<1.34.0)"] +emr-serverless = ["mypy-boto3-emr-serverless (>=1.33.0,<1.34.0)"] +entityresolution = ["mypy-boto3-entityresolution (>=1.33.0,<1.34.0)"] +es = ["mypy-boto3-es (>=1.33.0,<1.34.0)"] +essential = ["mypy-boto3-cloudformation (>=1.33.0,<1.34.0)", "mypy-boto3-dynamodb (>=1.33.0,<1.34.0)", "mypy-boto3-ec2 (>=1.33.0,<1.34.0)", "mypy-boto3-lambda (>=1.33.0,<1.34.0)", "mypy-boto3-rds (>=1.33.0,<1.34.0)", "mypy-boto3-s3 (>=1.33.0,<1.34.0)", "mypy-boto3-sqs (>=1.33.0,<1.34.0)"] +events = ["mypy-boto3-events (>=1.33.0,<1.34.0)"] +evidently = ["mypy-boto3-evidently (>=1.33.0,<1.34.0)"] +finspace = ["mypy-boto3-finspace (>=1.33.0,<1.34.0)"] +finspace-data = ["mypy-boto3-finspace-data (>=1.33.0,<1.34.0)"] +firehose = ["mypy-boto3-firehose (>=1.33.0,<1.34.0)"] +fis = ["mypy-boto3-fis (>=1.33.0,<1.34.0)"] +fms = ["mypy-boto3-fms (>=1.33.0,<1.34.0)"] +forecast = ["mypy-boto3-forecast (>=1.33.0,<1.34.0)"] +forecastquery = ["mypy-boto3-forecastquery (>=1.33.0,<1.34.0)"] +frauddetector = ["mypy-boto3-frauddetector (>=1.33.0,<1.34.0)"] +freetier = ["mypy-boto3-freetier (>=1.33.0,<1.34.0)"] +fsx = ["mypy-boto3-fsx (>=1.33.0,<1.34.0)"] +gamelift = ["mypy-boto3-gamelift (>=1.33.0,<1.34.0)"] +glacier = ["mypy-boto3-glacier (>=1.33.0,<1.34.0)"] +globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.33.0,<1.34.0)"] +glue = ["mypy-boto3-glue (>=1.33.0,<1.34.0)"] +grafana = ["mypy-boto3-grafana (>=1.33.0,<1.34.0)"] +greengrass = ["mypy-boto3-greengrass (>=1.33.0,<1.34.0)"] +greengrassv2 = ["mypy-boto3-greengrassv2 (>=1.33.0,<1.34.0)"] +groundstation = ["mypy-boto3-groundstation (>=1.33.0,<1.34.0)"] +guardduty = ["mypy-boto3-guardduty (>=1.33.0,<1.34.0)"] +health = ["mypy-boto3-health (>=1.33.0,<1.34.0)"] +healthlake = ["mypy-boto3-healthlake (>=1.33.0,<1.34.0)"] +honeycode = ["mypy-boto3-honeycode (>=1.33.0,<1.34.0)"] +iam = ["mypy-boto3-iam (>=1.33.0,<1.34.0)"] +identitystore = ["mypy-boto3-identitystore (>=1.33.0,<1.34.0)"] +imagebuilder = ["mypy-boto3-imagebuilder (>=1.33.0,<1.34.0)"] +importexport = ["mypy-boto3-importexport (>=1.33.0,<1.34.0)"] +inspector = ["mypy-boto3-inspector (>=1.33.0,<1.34.0)"] +inspector-scan = ["mypy-boto3-inspector-scan (>=1.33.0,<1.34.0)"] +inspector2 = ["mypy-boto3-inspector2 (>=1.33.0,<1.34.0)"] +internetmonitor = ["mypy-boto3-internetmonitor (>=1.33.0,<1.34.0)"] +iot = ["mypy-boto3-iot (>=1.33.0,<1.34.0)"] +iot-data = ["mypy-boto3-iot-data (>=1.33.0,<1.34.0)"] +iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.33.0,<1.34.0)"] +iot-roborunner = ["mypy-boto3-iot-roborunner (>=1.33.0,<1.34.0)"] +iot1click-devices = ["mypy-boto3-iot1click-devices (>=1.33.0,<1.34.0)"] +iot1click-projects = ["mypy-boto3-iot1click-projects (>=1.33.0,<1.34.0)"] +iotanalytics = ["mypy-boto3-iotanalytics (>=1.33.0,<1.34.0)"] +iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (>=1.33.0,<1.34.0)"] +iotevents = ["mypy-boto3-iotevents (>=1.33.0,<1.34.0)"] +iotevents-data = ["mypy-boto3-iotevents-data (>=1.33.0,<1.34.0)"] +iotfleethub = ["mypy-boto3-iotfleethub (>=1.33.0,<1.34.0)"] +iotfleetwise = ["mypy-boto3-iotfleetwise (>=1.33.0,<1.34.0)"] +iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (>=1.33.0,<1.34.0)"] +iotsitewise = ["mypy-boto3-iotsitewise (>=1.33.0,<1.34.0)"] +iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.33.0,<1.34.0)"] +iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.33.0,<1.34.0)"] +iotwireless = ["mypy-boto3-iotwireless (>=1.33.0,<1.34.0)"] +ivs = ["mypy-boto3-ivs (>=1.33.0,<1.34.0)"] +ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.33.0,<1.34.0)"] +ivschat = ["mypy-boto3-ivschat (>=1.33.0,<1.34.0)"] +kafka = ["mypy-boto3-kafka (>=1.33.0,<1.34.0)"] +kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.33.0,<1.34.0)"] +kendra = ["mypy-boto3-kendra (>=1.33.0,<1.34.0)"] +kendra-ranking = ["mypy-boto3-kendra-ranking (>=1.33.0,<1.34.0)"] +keyspaces = ["mypy-boto3-keyspaces (>=1.33.0,<1.34.0)"] +kinesis = ["mypy-boto3-kinesis (>=1.33.0,<1.34.0)"] +kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (>=1.33.0,<1.34.0)"] +kinesis-video-media = ["mypy-boto3-kinesis-video-media (>=1.33.0,<1.34.0)"] +kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (>=1.33.0,<1.34.0)"] +kinesis-video-webrtc-storage = ["mypy-boto3-kinesis-video-webrtc-storage (>=1.33.0,<1.34.0)"] +kinesisanalytics = ["mypy-boto3-kinesisanalytics (>=1.33.0,<1.34.0)"] +kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (>=1.33.0,<1.34.0)"] +kinesisvideo = ["mypy-boto3-kinesisvideo (>=1.33.0,<1.34.0)"] +kms = ["mypy-boto3-kms (>=1.33.0,<1.34.0)"] +lakeformation = ["mypy-boto3-lakeformation (>=1.33.0,<1.34.0)"] +lambda = ["mypy-boto3-lambda (>=1.33.0,<1.34.0)"] +launch-wizard = ["mypy-boto3-launch-wizard (>=1.33.0,<1.34.0)"] +lex-models = ["mypy-boto3-lex-models (>=1.33.0,<1.34.0)"] +lex-runtime = ["mypy-boto3-lex-runtime (>=1.33.0,<1.34.0)"] +lexv2-models = ["mypy-boto3-lexv2-models (>=1.33.0,<1.34.0)"] +lexv2-runtime = ["mypy-boto3-lexv2-runtime (>=1.33.0,<1.34.0)"] +license-manager = ["mypy-boto3-license-manager (>=1.33.0,<1.34.0)"] +license-manager-linux-subscriptions = ["mypy-boto3-license-manager-linux-subscriptions (>=1.33.0,<1.34.0)"] +license-manager-user-subscriptions = ["mypy-boto3-license-manager-user-subscriptions (>=1.33.0,<1.34.0)"] +lightsail = ["mypy-boto3-lightsail (>=1.33.0,<1.34.0)"] +location = ["mypy-boto3-location (>=1.33.0,<1.34.0)"] +logs = ["mypy-boto3-logs (>=1.33.0,<1.34.0)"] +lookoutequipment = ["mypy-boto3-lookoutequipment (>=1.33.0,<1.34.0)"] +lookoutmetrics = ["mypy-boto3-lookoutmetrics (>=1.33.0,<1.34.0)"] +lookoutvision = ["mypy-boto3-lookoutvision (>=1.33.0,<1.34.0)"] +m2 = ["mypy-boto3-m2 (>=1.33.0,<1.34.0)"] +machinelearning = ["mypy-boto3-machinelearning (>=1.33.0,<1.34.0)"] +macie2 = ["mypy-boto3-macie2 (>=1.33.0,<1.34.0)"] +managedblockchain = ["mypy-boto3-managedblockchain (>=1.33.0,<1.34.0)"] +managedblockchain-query = ["mypy-boto3-managedblockchain-query (>=1.33.0,<1.34.0)"] +marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.33.0,<1.34.0)"] +marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.33.0,<1.34.0)"] +marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.33.0,<1.34.0)"] +mediaconnect = ["mypy-boto3-mediaconnect (>=1.33.0,<1.34.0)"] +mediaconvert = ["mypy-boto3-mediaconvert (>=1.33.0,<1.34.0)"] +medialive = ["mypy-boto3-medialive (>=1.33.0,<1.34.0)"] +mediapackage = ["mypy-boto3-mediapackage (>=1.33.0,<1.34.0)"] +mediapackage-vod = ["mypy-boto3-mediapackage-vod (>=1.33.0,<1.34.0)"] +mediapackagev2 = ["mypy-boto3-mediapackagev2 (>=1.33.0,<1.34.0)"] +mediastore = ["mypy-boto3-mediastore (>=1.33.0,<1.34.0)"] +mediastore-data = ["mypy-boto3-mediastore-data (>=1.33.0,<1.34.0)"] +mediatailor = ["mypy-boto3-mediatailor (>=1.33.0,<1.34.0)"] +medical-imaging = ["mypy-boto3-medical-imaging (>=1.33.0,<1.34.0)"] +memorydb = ["mypy-boto3-memorydb (>=1.33.0,<1.34.0)"] +meteringmarketplace = ["mypy-boto3-meteringmarketplace (>=1.33.0,<1.34.0)"] +mgh = ["mypy-boto3-mgh (>=1.33.0,<1.34.0)"] +mgn = ["mypy-boto3-mgn (>=1.33.0,<1.34.0)"] +migration-hub-refactor-spaces = ["mypy-boto3-migration-hub-refactor-spaces (>=1.33.0,<1.34.0)"] +migrationhub-config = ["mypy-boto3-migrationhub-config (>=1.33.0,<1.34.0)"] +migrationhuborchestrator = ["mypy-boto3-migrationhuborchestrator (>=1.33.0,<1.34.0)"] +migrationhubstrategy = ["mypy-boto3-migrationhubstrategy (>=1.33.0,<1.34.0)"] +mobile = ["mypy-boto3-mobile (>=1.33.0,<1.34.0)"] +mq = ["mypy-boto3-mq (>=1.33.0,<1.34.0)"] +mturk = ["mypy-boto3-mturk (>=1.33.0,<1.34.0)"] +mwaa = ["mypy-boto3-mwaa (>=1.33.0,<1.34.0)"] +neptune = ["mypy-boto3-neptune (>=1.33.0,<1.34.0)"] +neptunedata = ["mypy-boto3-neptunedata (>=1.33.0,<1.34.0)"] +network-firewall = ["mypy-boto3-network-firewall (>=1.33.0,<1.34.0)"] +networkmanager = ["mypy-boto3-networkmanager (>=1.33.0,<1.34.0)"] +nimble = ["mypy-boto3-nimble (>=1.33.0,<1.34.0)"] +oam = ["mypy-boto3-oam (>=1.33.0,<1.34.0)"] +omics = ["mypy-boto3-omics (>=1.33.0,<1.34.0)"] +opensearch = ["mypy-boto3-opensearch (>=1.33.0,<1.34.0)"] +opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.33.0,<1.34.0)"] +opsworks = ["mypy-boto3-opsworks (>=1.33.0,<1.34.0)"] +opsworkscm = ["mypy-boto3-opsworkscm (>=1.33.0,<1.34.0)"] +organizations = ["mypy-boto3-organizations (>=1.33.0,<1.34.0)"] +osis = ["mypy-boto3-osis (>=1.33.0,<1.34.0)"] +outposts = ["mypy-boto3-outposts (>=1.33.0,<1.34.0)"] +panorama = ["mypy-boto3-panorama (>=1.33.0,<1.34.0)"] +payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.33.0,<1.34.0)"] +payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.33.0,<1.34.0)"] +pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.33.0,<1.34.0)"] +personalize = ["mypy-boto3-personalize (>=1.33.0,<1.34.0)"] +personalize-events = ["mypy-boto3-personalize-events (>=1.33.0,<1.34.0)"] +personalize-runtime = ["mypy-boto3-personalize-runtime (>=1.33.0,<1.34.0)"] +pi = ["mypy-boto3-pi (>=1.33.0,<1.34.0)"] +pinpoint = ["mypy-boto3-pinpoint (>=1.33.0,<1.34.0)"] +pinpoint-email = ["mypy-boto3-pinpoint-email (>=1.33.0,<1.34.0)"] +pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (>=1.33.0,<1.34.0)"] +pinpoint-sms-voice-v2 = ["mypy-boto3-pinpoint-sms-voice-v2 (>=1.33.0,<1.34.0)"] +pipes = ["mypy-boto3-pipes (>=1.33.0,<1.34.0)"] +polly = ["mypy-boto3-polly (>=1.33.0,<1.34.0)"] +pricing = ["mypy-boto3-pricing (>=1.33.0,<1.34.0)"] +privatenetworks = ["mypy-boto3-privatenetworks (>=1.33.0,<1.34.0)"] +proton = ["mypy-boto3-proton (>=1.33.0,<1.34.0)"] +qbusiness = ["mypy-boto3-qbusiness (>=1.33.0,<1.34.0)"] +qconnect = ["mypy-boto3-qconnect (>=1.33.0,<1.34.0)"] +qldb = ["mypy-boto3-qldb (>=1.33.0,<1.34.0)"] +qldb-session = ["mypy-boto3-qldb-session (>=1.33.0,<1.34.0)"] +quicksight = ["mypy-boto3-quicksight (>=1.33.0,<1.34.0)"] +ram = ["mypy-boto3-ram (>=1.33.0,<1.34.0)"] +rbin = ["mypy-boto3-rbin (>=1.33.0,<1.34.0)"] +rds = ["mypy-boto3-rds (>=1.33.0,<1.34.0)"] +rds-data = ["mypy-boto3-rds-data (>=1.33.0,<1.34.0)"] +redshift = ["mypy-boto3-redshift (>=1.33.0,<1.34.0)"] +redshift-data = ["mypy-boto3-redshift-data (>=1.33.0,<1.34.0)"] +redshift-serverless = ["mypy-boto3-redshift-serverless (>=1.33.0,<1.34.0)"] +rekognition = ["mypy-boto3-rekognition (>=1.33.0,<1.34.0)"] +repostspace = ["mypy-boto3-repostspace (>=1.33.0,<1.34.0)"] +resiliencehub = ["mypy-boto3-resiliencehub (>=1.33.0,<1.34.0)"] +resource-explorer-2 = ["mypy-boto3-resource-explorer-2 (>=1.33.0,<1.34.0)"] +resource-groups = ["mypy-boto3-resource-groups (>=1.33.0,<1.34.0)"] +resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (>=1.33.0,<1.34.0)"] +robomaker = ["mypy-boto3-robomaker (>=1.33.0,<1.34.0)"] +rolesanywhere = ["mypy-boto3-rolesanywhere (>=1.33.0,<1.34.0)"] +route53 = ["mypy-boto3-route53 (>=1.33.0,<1.34.0)"] +route53-recovery-cluster = ["mypy-boto3-route53-recovery-cluster (>=1.33.0,<1.34.0)"] +route53-recovery-control-config = ["mypy-boto3-route53-recovery-control-config (>=1.33.0,<1.34.0)"] +route53-recovery-readiness = ["mypy-boto3-route53-recovery-readiness (>=1.33.0,<1.34.0)"] +route53domains = ["mypy-boto3-route53domains (>=1.33.0,<1.34.0)"] +route53resolver = ["mypy-boto3-route53resolver (>=1.33.0,<1.34.0)"] +rum = ["mypy-boto3-rum (>=1.33.0,<1.34.0)"] +s3 = ["mypy-boto3-s3 (>=1.33.0,<1.34.0)"] +s3control = ["mypy-boto3-s3control (>=1.33.0,<1.34.0)"] +s3outposts = ["mypy-boto3-s3outposts (>=1.33.0,<1.34.0)"] +sagemaker = ["mypy-boto3-sagemaker (>=1.33.0,<1.34.0)"] +sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.33.0,<1.34.0)"] +sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.33.0,<1.34.0)"] +sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (>=1.33.0,<1.34.0)"] +sagemaker-geospatial = ["mypy-boto3-sagemaker-geospatial (>=1.33.0,<1.34.0)"] +sagemaker-metrics = ["mypy-boto3-sagemaker-metrics (>=1.33.0,<1.34.0)"] +sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (>=1.33.0,<1.34.0)"] +savingsplans = ["mypy-boto3-savingsplans (>=1.33.0,<1.34.0)"] +scheduler = ["mypy-boto3-scheduler (>=1.33.0,<1.34.0)"] +schemas = ["mypy-boto3-schemas (>=1.33.0,<1.34.0)"] +sdb = ["mypy-boto3-sdb (>=1.33.0,<1.34.0)"] +secretsmanager = ["mypy-boto3-secretsmanager (>=1.33.0,<1.34.0)"] +securityhub = ["mypy-boto3-securityhub (>=1.33.0,<1.34.0)"] +securitylake = ["mypy-boto3-securitylake (>=1.33.0,<1.34.0)"] +serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.33.0,<1.34.0)"] +service-quotas = ["mypy-boto3-service-quotas (>=1.33.0,<1.34.0)"] +servicecatalog = ["mypy-boto3-servicecatalog (>=1.33.0,<1.34.0)"] +servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (>=1.33.0,<1.34.0)"] +servicediscovery = ["mypy-boto3-servicediscovery (>=1.33.0,<1.34.0)"] +ses = ["mypy-boto3-ses (>=1.33.0,<1.34.0)"] +sesv2 = ["mypy-boto3-sesv2 (>=1.33.0,<1.34.0)"] +shield = ["mypy-boto3-shield (>=1.33.0,<1.34.0)"] +signer = ["mypy-boto3-signer (>=1.33.0,<1.34.0)"] +simspaceweaver = ["mypy-boto3-simspaceweaver (>=1.33.0,<1.34.0)"] +sms = ["mypy-boto3-sms (>=1.33.0,<1.34.0)"] +sms-voice = ["mypy-boto3-sms-voice (>=1.33.0,<1.34.0)"] +snow-device-management = ["mypy-boto3-snow-device-management (>=1.33.0,<1.34.0)"] +snowball = ["mypy-boto3-snowball (>=1.33.0,<1.34.0)"] +sns = ["mypy-boto3-sns (>=1.33.0,<1.34.0)"] +sqs = ["mypy-boto3-sqs (>=1.33.0,<1.34.0)"] +ssm = ["mypy-boto3-ssm (>=1.33.0,<1.34.0)"] +ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.33.0,<1.34.0)"] +ssm-incidents = ["mypy-boto3-ssm-incidents (>=1.33.0,<1.34.0)"] +ssm-sap = ["mypy-boto3-ssm-sap (>=1.33.0,<1.34.0)"] +sso = ["mypy-boto3-sso (>=1.33.0,<1.34.0)"] +sso-admin = ["mypy-boto3-sso-admin (>=1.33.0,<1.34.0)"] +sso-oidc = ["mypy-boto3-sso-oidc (>=1.33.0,<1.34.0)"] +stepfunctions = ["mypy-boto3-stepfunctions (>=1.33.0,<1.34.0)"] +storagegateway = ["mypy-boto3-storagegateway (>=1.33.0,<1.34.0)"] +sts = ["mypy-boto3-sts (>=1.33.0,<1.34.0)"] +support = ["mypy-boto3-support (>=1.33.0,<1.34.0)"] +support-app = ["mypy-boto3-support-app (>=1.33.0,<1.34.0)"] +swf = ["mypy-boto3-swf (>=1.33.0,<1.34.0)"] +synthetics = ["mypy-boto3-synthetics (>=1.33.0,<1.34.0)"] +textract = ["mypy-boto3-textract (>=1.33.0,<1.34.0)"] +timestream-query = ["mypy-boto3-timestream-query (>=1.33.0,<1.34.0)"] +timestream-write = ["mypy-boto3-timestream-write (>=1.33.0,<1.34.0)"] +tnb = ["mypy-boto3-tnb (>=1.33.0,<1.34.0)"] +transcribe = ["mypy-boto3-transcribe (>=1.33.0,<1.34.0)"] +transfer = ["mypy-boto3-transfer (>=1.33.0,<1.34.0)"] +translate = ["mypy-boto3-translate (>=1.33.0,<1.34.0)"] +trustedadvisor = ["mypy-boto3-trustedadvisor (>=1.33.0,<1.34.0)"] +verifiedpermissions = ["mypy-boto3-verifiedpermissions (>=1.33.0,<1.34.0)"] +voice-id = ["mypy-boto3-voice-id (>=1.33.0,<1.34.0)"] +vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.33.0,<1.34.0)"] +waf = ["mypy-boto3-waf (>=1.33.0,<1.34.0)"] +waf-regional = ["mypy-boto3-waf-regional (>=1.33.0,<1.34.0)"] +wafv2 = ["mypy-boto3-wafv2 (>=1.33.0,<1.34.0)"] +wellarchitected = ["mypy-boto3-wellarchitected (>=1.33.0,<1.34.0)"] +wisdom = ["mypy-boto3-wisdom (>=1.33.0,<1.34.0)"] +workdocs = ["mypy-boto3-workdocs (>=1.33.0,<1.34.0)"] +worklink = ["mypy-boto3-worklink (>=1.33.0,<1.34.0)"] +workmail = ["mypy-boto3-workmail (>=1.33.0,<1.34.0)"] +workmailmessageflow = ["mypy-boto3-workmailmessageflow (>=1.33.0,<1.34.0)"] +workspaces = ["mypy-boto3-workspaces (>=1.33.0,<1.34.0)"] +workspaces-thin-client = ["mypy-boto3-workspaces-thin-client (>=1.33.0,<1.34.0)"] +workspaces-web = ["mypy-boto3-workspaces-web (>=1.33.0,<1.34.0)"] +xray = ["mypy-boto3-xray (>=1.33.0,<1.34.0)"] + +[[package]] +name = "botocore" +version = "1.33.3" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.33.3-py3-none-any.whl", hash = "sha256:4aed37802aaae325a5abab33de2d7e68acf637086876727a164ea38a1cc219f9"}, + {file = "botocore-1.33.3.tar.gz", hash = "sha256:462528fc8dc1953bc19841fd2ccee1626ec8f5b13d9e451e13452c71de2fe0dc"}, +] + +[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.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +crt = ["awscrt (==0.19.17)"] + +[[package]] +name = "botocore-stubs" +version = "1.33.3" +description = "Type annotations and code completion for botocore" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "botocore_stubs-1.33.3-py3-none-any.whl", hash = "sha256:aea5a5f3616b41193f0211ccbdaffde3d68517526409ff7b12c34263677b619d"}, + {file = "botocore_stubs-1.33.3.tar.gz", hash = "sha256:c0bd46bff13f3e1eea41addfb2e8adb9ef99cb3ba27226ed841a5ef45196f596"}, +] + +[package.dependencies] +types-awscrt = "*" + +[package.extras] +botocore = ["botocore"] + +[[package]] +name = "bumpver" +version = "2022.1120" +description = "Bump version numbers in project files." +optional = false +python-versions = ">=2.7" +files = [ + {file = "bumpver-2022.1120-py2.py3-none-any.whl", hash = "sha256:9da18a6997ade04c66bec05f5349acc5f2f146b16fb77b307f91ef3370c6aa55"}, + {file = "bumpver-2022.1120.tar.gz", hash = "sha256:ff8ad562a2ed87e862e07683cb68c4b61046679bf155f7a7ebb20b2ea47775cd"}, +] + +[package.dependencies] +click = {version = "*", markers = "python_version >= \"3.6\""} +colorama = ">=0.4" +lexid = "*" +pathlib2 = "*" +setuptools = {version = ">=45.0.0", markers = "python_version >= \"3.5\""} +toml = "*" + +[[package]] +name = "certifi" +version = "2023.11.17" +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"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +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"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastjsonschema" +version = "2.19.0" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.19.0-py3-none-any.whl", hash = "sha256:b9fd1a2dd6971dbc7fee280a95bd199ae0dd9ce22beb91cc75e9c1c528a5170e"}, + {file = "fastjsonschema-2.19.0.tar.gz", hash = "sha256:e25df6647e1bc4a26070b700897b07b542ec898dd4f1f6ea013e7f6a88417225"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "idna" +version = "3.6" +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"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[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 = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "lexid" +version = "2021.1006" +description = "Variable width build numbers with lexical ordering." +optional = false +python-versions = ">=2.7" +files = [ + {file = "lexid-2021.1006-py2.py3-none-any.whl", hash = "sha256:5526bb5606fd74c7add23320da5f02805bddd7c77916f2dc1943e6bada8605ed"}, + {file = "lexid-2021.1006.tar.gz", hash = "sha256:509a3a4cc926d3dbf22b203b18a4c66c25e6473fb7c0e0d30374533ac28bafe5"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy-boto3-dynamodb" +version = "1.33.0" +description = "Type annotations for boto3.DynamoDB 1.33.0 service generated with mypy-boto3-builder 7.20.3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-boto3-dynamodb-1.33.0.tar.gz", hash = "sha256:2cfe1089c89de61b1ec0e69a72ba3e6865a013ea0a37d318ab564983785d42f9"}, + {file = "mypy_boto3_dynamodb-1.33.0-py3-none-any.whl", hash = "sha256:619ea2cc311ced0ecb44b6e8d3bf3dd851fb7c53a34128b4ff6d6e6a11fdd41f"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + +[[package]] +name = "otello" +version = "2.0.0" +description = "" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.dependencies] +boto3 = "*" +click = "*" +pyyaml = "*" +requests = "*" +urllib3 = "*" + +[package.source] +type = "git" +url = "https://github.com/hysds/otello.git" +reference = "develop" +resolved_reference = "56f5e992793df2aa9e9367972492291f545ecc35" + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathlib2" +version = "2.3.7.post1" +description = "Object-oriented filesystem paths" +optional = false +python-versions = "*" +files = [ + {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"}, + {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "platformdirs" +version = "4.0.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pluggy" +version = "1.3.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"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pylint" +version = "2.17.7" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, +] + +[package.dependencies] +astroid = ">=2.15.8,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, +] +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +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\""} + +[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.8.2" +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.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "0.21.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-dotenv-0.21.1.tar.gz", hash = "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49"}, + {file = "python_dotenv-0.21.1-py3-none-any.whl", hash = "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "s3transfer" +version = "0.8.2" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.8.2-py3-none-any.whl", hash = "sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76"}, + {file = "s3transfer-0.8.2.tar.gz", hash = "sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + +[[package]] +name = "setuptools" +version = "69.0.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "swodlr-common" +version = "0.1.1-alpha5" +description = "A support library for SWODLR Python microservices containing common utilities, models, and JSON schemas" +optional = false +python-versions = "^3.9" +files = [] +develop = false + +[package.dependencies] +boto3 = "^1.28.38" +fastjsonschema = "^2.18.0" +requests = "^2.31.0" + +[package.source] +type = "git" +url = "https://github.com/podaac/swodlr-common-py.git" +reference = "develop" +resolved_reference = "3d03bf90ea4e9360e7e7bc95f438753433fdb55a" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.3" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, + {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, +] + +[[package]] +name = "types-awscrt" +version = "0.19.18" +description = "Type annotations and code completion for awscrt" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "types_awscrt-0.19.18-py3-none-any.whl", hash = "sha256:fff11b4c075d736b0406fa8d87e7ee36b68b1652c2198e24e3de695ce68b223c"}, + {file = "types_awscrt-0.19.18.tar.gz", hash = "sha256:4f5a59c87c0582b69ebf994d614e0fb76cc2d0a5ec3f32e4d1db1c091867cdd3"}, +] + +[[package]] +name = "types-s3transfer" +version = "0.8.2" +description = "Type annotations and code completion for s3transfer" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "types_s3transfer-0.8.2-py3-none-any.whl", hash = "sha256:5e084ebcf2704281c71b19d5da6e1544b50859367d034b50080d5316a76a9418"}, + {file = "types_s3transfer-0.8.2.tar.gz", hash = "sha256:2e41756fcf94775a9949afa856489ac4570308609b0493dfbd7b4d333eb423e6"}, +] + +[[package]] +name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] + +[[package]] +name = "urllib3" +version = "1.26.18" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, + {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, +] + +[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.0.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "e409412bec7b27bead9b9a4c8ce1ae4b1018a2fcbcb0f2b069290ad54a25bc31" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..59f1a0a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[tool.poetry] +name = "swodlr-ingest-to-sds" +version = "0.0.2-alpha14" +description = "A system for ingesting input products for the SWOT SDS" +authors = ["podaac-tva "] +license = "Apache-2.0" +readme = "README.md" +packages = [ + { include = "podaac" } +] + +[tool.poetry.dependencies] +python = "^3.9" +otello = {git = "https://github.com/hysds/otello.git", branch = "develop"} +boto3-stubs-lite = {extras = ["dynamodb"], version = "^1.26.81"} +swodlr-common = {git = "https://github.com/podaac/swodlr-common-py.git", rev = "develop"} + +[tool.poetry.group.dev.dependencies] +boto3 = "^1.26.59" +pytest = "^7.2.1" +exceptiongroup = { version = "^1.1.0", python = "<3.11" } +python-dotenv = "^0.21.1" +flake8 = "^6.0.0" +bumpver = "^2022.1120" +pylint = "^2.16.2" + +[tool.poetry.scripts] +ingest = 'podaac.swodlr_ingest_to_sds.__main__:main' + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..5eb8fb2 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.28.0" + constraints = ">= 5.28.0" + hashes = [ + "h1:xIGISViKIAzm5yJ9PZejQDDxwkVMwp1CSxINPP18Fc8=", + "zh:062171f23f3e9d09dde4bdef4e2e1be6c10ce5392e5acb2d5674ca8d18e4efe2", + "zh:081f9aa09f571a95334c13eb11f7dd9e421250e5c64b2005509638eee382ccd7", + "zh:115f73d02f240f6626e9e4b4551dab9618a713cc238e0340155b9468b16da785", + "zh:1372084815a5f2e795edc1020969401786ca9032a510e0543d1e048fd699c565", + "zh:177a2fd380bec9fcda440d028fdf13db701d054ca637cdc860b70d62d3caafcf", + "zh:18274cf43f8bb0a48da25a8f511020aa4a3052582be4e48eeff4c914c0e10a31", + "zh:2f9d8e5b5375da4528e9ae437bbf93c2be91a50f814ca61046f3b2d16aabb3cb", + "zh:565a4d9e124f118fef41bd2c82e9ae3ea7316821db8f3a03838f84af7db72efb", + "zh:62f9f297c0ce50720e2380bd36fa1f27a210cfac08e993b0dcdb85ecf6559e07", + "zh:8a185766ecd16752aff72260e55e3df28a3d7e4bf28e357fbf9c0460b7ed5b39", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9b698d94915a5077d1c10a705b8d449f719eb87f25f6d46ff165b6bb9fb12778", + "zh:ba2c2ad8f160d9f57eaefde2171cf35697e4912f15c5cafd0ef471d1f38531f9", + "zh:d78d25aeed4851907817f6b281598ed853a60ca65c6bd711c8539ca3f55a841f", + "zh:f743437743605727edcc77c02e3a60358c222311f7a3015e883601e4e4844c1e", + ] +} + +provider "registry.terraform.io/hashicorp/local" { + version = "2.4.0" + hashes = [ + "h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=", + "zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9", + "zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf", + "zh:70a6f6a852dd83768d0778ce9817d81d4b3f073fab8fa570bff92dcb0824f732", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:82a803f2f484c8b766e2e9c32343e9c89b91997b9f8d2697f9f3837f62926b35", + "zh:9708a4e40d6cc4b8afd1352e5186e6e1502f6ae599867c120967aebe9d90ed04", + "zh:973f65ce0d67c585f4ec250c1e634c9b22d9c4288b484ee2a871d7fa1e317406", + "zh:c8fa0f98f9316e4cfef082aa9b785ba16e36ff754d6aba8b456dab9500e671c6", + "zh:cfa5342a5f5188b20db246c73ac823918c189468e1382cb3c48a9c0c08fc5bf7", + "zh:e0e2b477c7e899c63b06b38cd8684a893d834d6d0b5e9b033cedc06dd7ffe9e2", + "zh:f62d7d05ea1ee566f732505200ab38d94315a4add27947a60afa29860822d3fc", + "zh:fa7ce69dde358e172bd719014ad637634bbdabc49363104f4fca759b4b73f2ce", + ] +} diff --git a/terraform/bin/config.sh b/terraform/bin/config.sh new file mode 100755 index 0000000..f8165e1 --- /dev/null +++ b/terraform/bin/config.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eo pipefail + +if [ ! $# -eq 1 ] +then + echo "usage: $(caller | cut -d' ' -f2) venue" + exit 1 +fi + +VENUE=$1 +source "$(dirname $BASH_SOURCE)/../environments/$VENUE.env" + +export TF_IN_AUTOMATION=true # https://www.terraform.io/cli/config/environment-variables#tf_in_automation +export TF_INPUT=false # https://www.terraform.io/cli/config/environment-variables#tf_input + +export TF_VAR_region="$REGION" +export TF_VAR_stage="$VENUE" +export TF_VAR_sds_pcm_release_tag="$SWODLR_sds_pcm_release_tag" + +terraform init -reconfigure -backend-config="bucket=$BUCKET" -backend-config="region=$REGION" \ No newline at end of file diff --git a/terraform/bin/deploy.sh b/terraform/bin/deploy.sh new file mode 100755 index 0000000..048635c --- /dev/null +++ b/terraform/bin/deploy.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail + +source "$(dirname $BASH_SOURCE)/config.sh" +terraform apply diff --git a/terraform/bin/destroy.sh b/terraform/bin/destroy.sh new file mode 100755 index 0000000..415884f --- /dev/null +++ b/terraform/bin/destroy.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail + +source "$(dirname $BASH_SOURCE)/config.sh" +terraform destroy diff --git a/terraform/database_dynamodb.tf b/terraform/database_dynamodb.tf new file mode 100644 index 0000000..d78bf77 --- /dev/null +++ b/terraform/database_dynamodb.tf @@ -0,0 +1,11 @@ +# -- Tables -- + +// This is mapped from the Terraform infrastructure defined in the +// podaac/swodlr-api repo +data "aws_dynamodb_table" "ingest" { + name = "${local.app_prefix}-ingest" +} + +data "aws_dynamodb_table" "available_tiles" { + name = "${local.app_prefix}-available-tiles" +} diff --git a/terraform/environments/ops.env b/terraform/environments/ops.env new file mode 100644 index 0000000..4515b04 --- /dev/null +++ b/terraform/environments/ops.env @@ -0,0 +1,3 @@ +export REGION=us-west-2 +export BUCKET=podaac-services-ops-terraform +export SWODLR_sds_pcm_release_tag=pcm-v5.0.0-pge-v5.0.4 \ No newline at end of file diff --git a/terraform/environments/sit.env b/terraform/environments/sit.env new file mode 100644 index 0000000..6aea454 --- /dev/null +++ b/terraform/environments/sit.env @@ -0,0 +1,3 @@ +export REGION=us-west-2 +export BUCKET=podaac-services-sit-terraform +export SWODLR_sds_pcm_release_tag=pcm-v5.0.0-pge-v5.0.4 diff --git a/terraform/environments/uat.env b/terraform/environments/uat.env new file mode 100644 index 0000000..daa52bf --- /dev/null +++ b/terraform/environments/uat.env @@ -0,0 +1,3 @@ +export REGION=us-west-2 +export BUCKET=podaac-services-uat-terraform +export SWODLR_sds_pcm_release_tag=pcm-v5.0.0-pge-v5.0.4 diff --git a/terraform/lambdas.tf b/terraform/lambdas.tf new file mode 100644 index 0000000..54e34f6 --- /dev/null +++ b/terraform/lambdas.tf @@ -0,0 +1,240 @@ +# -- Lambdas -- +resource "aws_lambda_function" "bootstrap" { + function_name = "${local.service_prefix}-bootstrap" + handler = "podaac.swodlr_ingest_to_sds.bootstrap.lambda_handler" + + role = aws_iam_role.bootstrap.arn + runtime = "python3.9" + + filename = "${path.module}/../dist/${local.name}-${local.version}.zip" + source_code_hash = filebase64sha256("${path.module}/../dist/${local.name}-${local.version}.zip") +} + +resource "aws_lambda_function" "submit_to_sds" { + function_name = "${local.service_prefix}-submit_to_sds" + handler = "podaac.swodlr_ingest_to_sds.submit_to_sds.lambda_handler" + + role = aws_iam_role.lambda.arn + runtime = "python3.9" + + filename = "${path.module}/../dist/${local.name}-${local.version}.zip" + source_code_hash = filebase64sha256("${path.module}/../dist/${local.name}-${local.version}.zip") + + vpc_config { + security_group_ids = [aws_security_group.default.id] + subnet_ids = data.aws_subnets.private.ids + } +} + +resource "aws_lambda_function" "poll_status" { + function_name = "${local.service_prefix}-poll_status" + handler = "podaac.swodlr_ingest_to_sds.poll_status.lambda_handler" + + role = aws_iam_role.lambda.arn + runtime = "python3.9" + + filename = "${path.module}/../dist/${local.name}-${local.version}.zip" + source_code_hash = filebase64sha256("${path.module}/../dist/${local.name}-${local.version}.zip") + + vpc_config { + security_group_ids = [aws_security_group.default.id] + subnet_ids = data.aws_subnets.private.ids + } +} + +# -- IAM -- +resource "aws_iam_policy" "ssm_parameters_read" { + name = "SSMParametersReadOnlyAccess" + path = "${local.service_path}/" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "" + Action = [ + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:GetParametersByPath" + ] + Effect = "Allow" + Resource = "arn:aws:ssm:${var.region}:${local.account_id}:parameter${local.service_path}/*" + }] + }) +} + +resource "aws_iam_policy" "lambda_networking" { + name = "LambdaNetworkAccess" + path = "${local.service_path}/" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = [ + "ec2:DescribeInstances", + "ec2:CreateNetworkInterface", + "ec2:AttachNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface" + ] + Effect = "Allow" + Resource = "*" + }] + }) +} + +resource "aws_iam_role" "bootstrap" { + name = "bootstrap" + path = "${local.service_path}/" + + permissions_boundary = "arn:aws:iam::${local.account_id}:policy/NGAPShRoleBoundary" + managed_policy_arns = [ + "arn:aws:iam::${local.account_id}:policy/NGAPProtAppInstanceMinimalPolicy", + aws_iam_policy.ssm_parameters_read.arn + ] + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }] + }) + + inline_policy { + name = "BootstrapPolicy" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "" + Action = "states:StartExecution" + Effect = "Allow" + Resource = "arn:aws:states:${var.region}:${local.account_id}:stateMachine:${aws_sfn_state_machine.ingest_to_sds.name}" + }, + + { + Sid = "" + Action = [ + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes" + ] + Effect = "Allow" + Resource = "arn:aws:sqs:${var.region}:${local.account_id}:${data.aws_sqs_queue.ingest.name}" + } + ] + }) + } +} + +resource "aws_iam_role" "lambda" { + name = "lambda" + path = "${local.service_path}/" + + permissions_boundary = "arn:aws:iam::${local.account_id}:policy/NGAPShRoleBoundary" + managed_policy_arns = [ + "arn:aws:iam::${local.account_id}:policy/NGAPProtAppInstanceMinimalPolicy", + aws_iam_policy.ssm_parameters_read.arn, + aws_iam_policy.lambda_networking.arn + ] + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + }] + }) + + inline_policy { + name = "LambdaPolicy" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "" + Action = [ + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem" + ] + Effect = "Allow" + Resource = data.aws_dynamodb_table.ingest.arn + }, + { + Sid = "" + Action = [ + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem" + ] + Effect = "Allow" + Resource = data.aws_dynamodb_table.available_tiles.arn + } + ] + }) + } +} + +# -- SSM Parameters -- +resource "aws_ssm_parameter" "sds_pcm_release_tag" { + name = "${local.service_path}/sds_pcm_release_tag" + type = "String" + value = var.sds_pcm_release_tag +} + +resource "aws_ssm_parameter" "sds_host" { + name = "${local.service_path}/sds_host" + type = "String" + value = var.sds_host +} + +resource "aws_ssm_parameter" "sds_username" { + name = "${local.service_path}/sds_username" + type = "String" + value = var.sds_username +} + +resource "aws_ssm_parameter" "sds_password" { + name = "${local.service_path}/sds_password" + type = "SecureString" + value = var.sds_password +} + +resource "aws_ssm_parameter" "sds_ca_cert" { + name = "${local.service_path}/sds_ca_cert" + type = "SecureString" + value = local.sds_ca_cert +} + +resource "aws_ssm_parameter" "stepfunction_arn" { + name = "${local.service_path}/stepfunction_arn" + type = "String" + value = aws_sfn_state_machine.ingest_to_sds.arn +} + +resource "aws_ssm_parameter" "ingest_queue_url" { + name = "${local.service_path}/ingest_queue_url" + type = "String" + value = data.aws_sqs_queue.ingest.id +} + +resource "aws_ssm_parameter" "ingest_table_name" { + name = "${local.service_path}/ingest_table_name" + type = "String" + value = data.aws_dynamodb_table.ingest.name +} + +resource "aws_ssm_parameter" "available_tiles_table_name" { + name = "${local.service_path}/available_tiles_table_name" + type = "String" + value = data.aws_dynamodb_table.available_tiles.name +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..b90bc06 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,53 @@ +terraform { + required_version = ">=1.2.7" + + backend "s3" { + key = "services/swodlr/ingest-to-sds/terraform.tfstate" + } + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">=5.28.0" + } + } +} + +provider "aws" { + region = var.region + + default_tags { + tags = local.default_tags + } + + ignore_tags { + key_prefixes = ["gsfc-ngap"] + } +} + +data "aws_caller_identity" "current" {} + +data "local_file" "pyproject_toml" { + filename = abspath("${path.root}/../pyproject.toml") +} + +locals { + name = regex("name = \"(\\S*)\"", data.local_file.pyproject_toml.content)[0] + version = regex("version = \"(\\S*)\"", data.local_file.pyproject_toml.content)[0] + environment = var.stage + + app_prefix = "service-${var.app_name}-${local.environment}" + service_prefix = "service-${var.app_name}-${local.environment}-${var.service_name}" + service_path = "/service/${var.app_name}/${var.service_name}" + + sds_ca_cert = file(var.sds_ca_cert_path) + + account_id = data.aws_caller_identity.current.account_id + + default_tags = length(var.default_tags) == 0 ? { + team = "TVA" + application = local.app_prefix + version = local.version + Environment = local.environment + } : var.default_tags +} diff --git a/terraform/messaging.tf b/terraform/messaging.tf new file mode 100644 index 0000000..6bc00f1 --- /dev/null +++ b/terraform/messaging.tf @@ -0,0 +1,13 @@ +# -- SQS -- + +// This is mapped from the Terraform infrastructure defined in the +// podaac/swodlr-api repo +data "aws_sqs_queue" "ingest" { + name = "${local.app_prefix}-ingest-queue" +} + +# -- Event Mapping -- +resource "aws_lambda_event_source_mapping" "ingest_queue" { + event_source_arn = data.aws_sqs_queue.ingest.arn + function_name = aws_lambda_function.bootstrap.arn +} diff --git a/terraform/network.tf b/terraform/network.tf new file mode 100644 index 0000000..b35328d --- /dev/null +++ b/terraform/network.tf @@ -0,0 +1,32 @@ +# -- VPC & Subnets -- +data "aws_vpc" "default" { + tags = { + "Name": "Application VPC" + } +} + +data "aws_subnets" "private" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } + + filter { + name = "tag:Name" + values = ["Private application*"] + } +} + +# -- Security Group -- +resource "aws_security_group" "default" { + name = "${local.service_path}/default" + vpc_id = data.aws_vpc.default.id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } +} \ No newline at end of file diff --git a/terraform/stepfunction.tf b/terraform/stepfunction.tf new file mode 100644 index 0000000..9efce7e --- /dev/null +++ b/terraform/stepfunction.tf @@ -0,0 +1,80 @@ +# -- Step Function -- +resource "aws_sfn_state_machine" "ingest_to_sds" { + name = local.service_prefix + role_arn = aws_iam_role.sfn.arn + + definition = jsonencode({ + StartAt = "SubmitToSDS" + States = { + SubmitToSDS = { + Type = "Task" + Resource = aws_lambda_function.submit_to_sds.arn + Next = "CheckJobs" + } + + CheckJobs = { + Type = "Choice", + Choices = [{ + Variable = "$.jobs[0]" + IsPresent = true + Next = "Wait" + }], + Default = "Done" + } + + Wait = { + Type = "Wait" + Seconds = 60 + Next = "PollStatus" + } + + PollStatus = { + Type = "Task" + Resource = aws_lambda_function.poll_status.arn + Next = "CheckJobs" + } + + Done = { + Type = "Succeed" + } + } + }) +} + +# -- IAM -- +resource "aws_iam_role" "sfn" { + name = "sfn" + path = "${local.service_path}/" + + permissions_boundary = "arn:aws:iam::${local.account_id}:policy/NGAPShRoleBoundary" + managed_policy_arns = [ + "arn:aws:iam::${local.account_id}:policy/NGAPProtAppInstanceMinimalPolicy" + ] + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "states.amazonaws.com" + } + }] + }) + + inline_policy { + name = "LambdasExecute" + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Sid = "" + Action = "lambda:InvokeFunction" + Effect = "Allow" + Resource = [ + aws_lambda_function.submit_to_sds.arn, + aws_lambda_function.poll_status.arn + ] + }] + }) + } +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..b553977 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,46 @@ +variable "app_name" { + default = "swodlr" + type = string +} + +variable "service_name" { + default = "ingest-to-sds" + type = string +} + +variable "default_tags" { + type = map(string) + default = {} +} + +variable "stage" { + type = string +} + +variable "region" { + type = string +} + +variable "sds_pcm_release_tag" { + type = string +} + +variable "sds_host" { + type = string + sensitive = true +} + +variable "sds_username" { + type = string + sensitive = true +} + +variable "sds_password" { + type = string + sensitive = true +} + +variable "sds_ca_cert_path" { + type = string + default = "/etc/ssl/certs/JPLICA.Root.pem" +} diff --git a/tests/data/invalid_sqs_event.json b/tests/data/invalid_sqs_event.json new file mode 100644 index 0000000..18f4509 --- /dev/null +++ b/tests/data/invalid_sqs_event.json @@ -0,0 +1,37 @@ +{ + "Records": [ + { + "messageId": "2c3de903-baf2-432c-9652-e3b4ccbfc34b", + "receiptHandle": "ABCDEF", + "body": "{\"identifier\":\"test-1\",\"product\":{\"files\":[{\"type\":\"metadata\",\"name\":\"test-1.zip\",\"uri\":\"https://domain.test/bucket/test/test-1.zip\",\"checksumType\":\"md5\",\"checksum\":\"\",\"size\":0}]}}", + "attributes": {}, + "messageAttributes": {}, + "md5OfBody": "", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:queue", + "awsRegion": "us-west-2" + }, + { + "messageId": "656f50b7-e353-46b8-b2fb-52dcf006e83c", + "receiptHandle": "GHIJKL", + "body": "{\"identifier\":\"test-2\",\"product\":{\"files\":[{\"type\":\"metadata\",\"name\":\"test-2.zip\",\"uri\":\"https://domain.test/bucket/test/test-2.zip\",\"checksumType\":\"md5\",\"checksum\":\"\",\"size\":0}]}}", + "attributes": {}, + "messageAttributes": {}, + "md5OfBody": "", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:queue", + "awsRegion": "us-east-2" + }, + { + "messageId": "0bfe9a13-51c0-477e-8798-9eddc71ea3f3", + "receiptHandle": "MNOPQR", + "body": "{\"identifier\":\"test-3\",\"product\":{\"files\":[{\"type\":\"metadata\",\"name\":\"test-3.zip\",\"uri\":\"https://domain.test/bucket/test/test-3.zip\",\"checksumType\":\"md5\",\"checksum\":\"\",\"size\":0}]}}", + "attributes": {}, + "messageAttributes": {}, + "md5OfBody": "", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:queue", + "awsRegion": "us-east-2" + } + ] +} \ No newline at end of file diff --git a/tests/data/poll_event.json b/tests/data/poll_event.json new file mode 100644 index 0000000..7f70c96 --- /dev/null +++ b/tests/data/poll_event.json @@ -0,0 +1,12 @@ +{ + "jobs": [ + { + "granule_id": "granule_id_1", + "job_id": "job_id_1" + }, + { + "granule_id": "granule_id_2", + "job_id": "job_id_2" + } + ] +} diff --git a/tests/data/valid_sqs_event.json b/tests/data/valid_sqs_event.json new file mode 100644 index 0000000..4bb8186 --- /dev/null +++ b/tests/data/valid_sqs_event.json @@ -0,0 +1,37 @@ +{ + "Records": [ + { + "messageId": "MessageID_1", + "receiptHandle": "ReceiptHandle_1", + "body": "{\"identifier\":\"test-1\",\"product\":{\"files\":[{\"type\":\"metadata\",\"name\":\"test-1.json\",\"uri\":\"https://domain.test/bucket/test/test-1.json\"},{\"type\":\"data\",\"name\":\"test-1.nc\",\"uri\":\"https://domain.test/bucket/test/test-1.nc\"}]}}", + "attributes": {}, + "messageAttributes": {}, + "md5OfBody": "", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:queue", + "awsRegion": "us-west-2" + }, + { + "messageId": "MessageID_2", + "receiptHandle": "ReceiptHandle_2", + "body": "{\"identifier\":\"test-2\",\"product\":{\"files\":[{\"type\":\"metadata\",\"name\":\"test-2.json\",\"uri\":\"https://domain.test/bucket/test/test-2.json\"},{\"type\":\"data\",\"name\":\"test-2.nc\",\"uri\":\"https://domain.test/bucket/test/test-2.nc\"}]}}", + "attributes": {}, + "messageAttributes": {}, + "md5OfBody": "", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:queue", + "awsRegion": "us-east-2" + }, + { + "messageId": "MessageID_3", + "receiptHandle": "ReceiptHandle_3", + "body": "{\"identifier\":\"test-3\",\"product\":{\"files\":[{\"type\":\"metadata\",\"name\":\"test-3.json\",\"uri\":\"https://domain.test/bucket/test/test-3.json\"},{\"type\":\"data\",\"name\":\"test-3.nc\",\"uri\":\"https://domain.test/bucket/test/test-3.nc\"}]}}", + "attributes": {}, + "messageAttributes": {}, + "md5OfBody": "", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-2:123456789012:queue", + "awsRegion": "us-east-2" + } + ] +} \ No newline at end of file diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py new file mode 100644 index 0000000..3a1f3da --- /dev/null +++ b/tests/test_bootstrap.py @@ -0,0 +1,42 @@ +'''Tests for the bootstrap module''' +import os +from unittest import TestCase +from unittest.mock import patch + +TEST_ARN = 'ABC123' + +with ( + patch('boto3.client'), + patch.dict(os.environ, { + 'SWODLR_ENV': 'dev', + 'SWODLR_stepfunction_arn': TEST_ARN + }) +): + from podaac.swodlr_ingest_to_sds import bootstrap + + +class TestBootstrap(TestCase): + '''Tests for the bootstrap module''' + + def test_bootstrap(self): + ''' + Test the lambda handler of the bootstrap module by submitting an event + and checking output for the transformed event + ''' + event = { + 'a': 42, + 'b': True, + 'c': 'ipsum lorem' + } + + with ( + patch.object( + bootstrap.stepfunctions, + 'start_execution' + ) as mock_exec + ): + bootstrap.lambda_handler(event, None) + mock_exec.assert_called_once_with( + stateMachineArn=TEST_ARN, + input='{"a":42,"b":true,"c":"ipsum lorem"}' + ) diff --git a/tests/test_poll_status.py b/tests/test_poll_status.py new file mode 100644 index 0000000..ea6bef4 --- /dev/null +++ b/tests/test_poll_status.py @@ -0,0 +1,70 @@ +'''Tests for the poll_status module''' +from unittest import TestCase +from unittest.mock import patch +from pathlib import Path +import json +from os import environ +from podaac.swodlr_ingest_to_sds.utilities import utils + +with ( + patch.dict(environ, { + 'SWODLR_ENV': 'dev', + 'SWODLR_sds_username': 'AAAAAA', + 'SWODLR_sds_password': 'BBBBBB' + }) +): + from podaac.swodlr_ingest_to_sds import poll_status + + +class TestPollStatus(TestCase): + '''Tests for the poll_status module''' + data_path = Path(__file__).parent.joinpath('data') + poll_event_path = data_path.joinpath('poll_event.json') + with open(poll_event_path, encoding='utf-8') as f: + poll_event = json.load(f) + + @patch('boto3.resource') + def test_poll_status(self, _): + ''' + Test the lambda handler for the poll_status module by submitting two + jobs, polling their status, verifying that the correct jobs are + updated in the database, and verifying that remaining jobs are + returned in the event. + ''' + _statuses = [ + {'status': 'job-completed'}, + {'status': 'job-started'} + ] + + def _mock_get_info(): + nonlocal _statuses + return _statuses.pop() + + event = None + with ( + patch('otello.mozart.Mozart.get_job_by_id') as mock_get_job_by_id, + ): + mock_get_job_by_id().get_info.side_effect = _mock_get_info + event = poll_status.lambda_handler(self.poll_event, None) + + self.assertEqual(len(event['jobs']), 1) + self.assertDictEqual(event['jobs'][0], { + 'job_id': 'job_id_1', + 'granule_id': 'granule_id_1' + }) + + valid_statuses = { + 'granule_id_1': 'job-started', + 'granule_id_2': 'job-completed' + } + + # pylint: disable=no-member + update_item_calls = utils.ingest_table.update_item.call_args_list + self.assertEqual(len(update_item_calls), 2) + for call in update_item_calls: + kwargs = call.kwargs + key = kwargs['Key']['granule_id'] + status = kwargs['ExpressionAttributeValues'][':status'] + + self.assertEqual(status, valid_statuses[key]) + del valid_statuses[key] diff --git a/tests/test_submit_to_sds.py b/tests/test_submit_to_sds.py new file mode 100644 index 0000000..a760772 --- /dev/null +++ b/tests/test_submit_to_sds.py @@ -0,0 +1,133 @@ +'''Tests for the submit_to_sds module''' +from unittest import TestCase +from unittest.mock import patch +from pathlib import Path +import json +from os import environ + + +with ( + patch('boto3.client'), + patch('otello.mozart.Mozart.get_job_type'), + patch.dict(environ, { + 'SWODLR_ENV': 'dev', + 'SWODLR_sds_username': 'test_username', + 'SWODLR_sds_password': 'test_password', + 'SWODLR_ingest_table_name': 'test_ingest_table_name', + 'SWODLR_ingest_queue_url': 'test_ingest_queue_url', + 'SWODLR_available_tiles_table': 'test_available_tiles_table' + }) +): + from podaac.swodlr_ingest_to_sds import submit_to_sds + + +class TestSubmitToSds(TestCase): + '''Tests for the submit_to_sds module''' + data_path = Path(__file__).parent.joinpath('data') + valid_event_path = data_path.joinpath('valid_sqs_event.json') + with open(valid_event_path, encoding='utf-8') as f: + valid_event = json.load(f) + + invalid_event_path = data_path.joinpath('invalid_sqs_event.json') + with open(invalid_event_path, encoding='utf-8') as f: + invalid_event = json.load(f) + + @patch('boto3.resource') + def test_valid_submit(self, _): + ''' + Test the lambda handler for the submit_to_sds module by submitting + three jobs, verifying all jobs are submitted, and verifying that + the correct items are added to the ingest table. + ''' + + submit_to_sds.lambda_handler(self.valid_event, None) + + # pylint: disable=no-member + submit_calls = submit_to_sds.ingest_job_type.submit_job.call_args_list + # pylint: disable=no-member + input_calls = submit_to_sds.ingest_job_type.set_input_params\ + .call_args_list + # pylint: disable=no-member,unnecessary-dunder-call + put_item_calls = submit_to_sds.utils.ingest_table.batch_writer()\ + .__enter__().put_item.call_args_list + + self.assertEqual(len(input_calls), 3) + self.assertEqual(len(submit_calls), 3) + self.assertEqual(len(put_item_calls), 3) + + valid_granule_ids = {'test-1', 'test-2', 'test-3'} + valid_filenames = {'test-1.nc', 'test-2.nc', 'test-3.nc'} + valid_urls = { + 's3://bucket/test/test-1.nc', + 's3://bucket/test/test-2.nc', + 's3://bucket/test/test-3.nc' + } + valid_tags = { + 'ingest_file_otello__test-1.nc', + 'ingest_file_otello__test-2.nc', + 'ingest_file_otello__test-3.nc' + } + + # set_input_params calls + _valid_filenames = valid_filenames.copy() + _valid_urls = valid_urls.copy() + for call in input_calls: + params = call.args[0] + self.assertIn(params['id'], _valid_filenames) + self.assertIn(params['data_file'], _valid_filenames) + self.assertIn(params['data_url'], _valid_urls) + + _valid_filenames.remove(params['data_file']) + _valid_urls.remove(params['data_url']) + + # put_item calls + _valid_granule_ids = valid_granule_ids.copy() + _valid_urls = valid_urls.copy() + for call in put_item_calls: + self.assertIn(call.kwargs['Item'] + ['granule_id'], _valid_granule_ids) + self.assertIn(call.kwargs['Item']['s3_url'], _valid_urls) + + _valid_granule_ids.remove(call.kwargs['Item']['granule_id']) + _valid_urls.remove(call.kwargs['Item']['s3_url']) + + # submit_job calls + _valid_tags = valid_tags.copy() + for call in submit_calls: + self.assertIn(call.kwargs['tag'], _valid_tags) + _valid_tags.remove(call.kwargs['tag']) + + # batch_get_item call + submit_to_sds.dynamodb.batch_get_item.assert_called_once_with( + RequestItems={ + 'test_ingest_table_name': { + 'Keys': [ + {'granule_id': {'S': 'test-1'}}, + {'granule_id': {'S': 'test-2'}}, + {'granule_id': {'S': 'test-3'}}, + ], + 'ProjectionExpression': 'granule_id, #status', + 'ExpressionAttributeNames': {'#status': 'status'} + } + }, + ReturnConsumedCapacity='NONE' + ) + + def test_invalid_submit(self): + ''' + Test the lambda handler for the submit_to_sds module by submitting + an invalid event, verifying that a RuntimeException is raised. + ''' + event = submit_to_sds.lambda_handler(self.invalid_event, None) + self.assertEqual(len(event['jobs']), 0) + + def tearDown(self): + ''' + Reset mocks after each test run + ''' + + # pylint: disable=no-member + submit_to_sds.ingest_job_type.set_input_params.reset_mock() + # pylint: disable=no-member + submit_to_sds.ingest_job_type.submit_job.reset_mock() + submit_to_sds.dynamodb.batch_get_item.reset_mock()