From b2afef20399d71fb914f5ec3d6069ca738db9fde Mon Sep 17 00:00:00 2001 From: Robert Aboukhalil Date: Fri, 6 Oct 2023 09:35:10 -0700 Subject: [PATCH 1/5] Add multipart upload --- entities/api/files.py | 99 ++++++++++++++++++++++++++++++------ entities/api/main.py | 4 +- platformics/api/core/deps.py | 9 ++++ 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/entities/api/files.py b/entities/api/files.py index 729d4237..4951b4ee 100644 --- a/entities/api/files.py +++ b/entities/api/files.py @@ -1,3 +1,4 @@ +import json import typing import database.models as db import strawberry @@ -12,7 +13,13 @@ from cerbos.sdk.client import CerbosClient from cerbos.sdk.model import Principal -from platformics.api.core.deps import get_cerbos_client, get_db_session, require_auth_principal, get_settings +from platformics.api.core.deps import ( + get_cerbos_client, + get_db_session, + require_auth_principal, + get_settings, + get_sts_client, +) from platformics.api.core.settings import APISettings from sqlalchemy.ext.asyncio import AsyncSession from platformics.security.authorization import CerbosAction, get_resource_query @@ -33,6 +40,17 @@ class SignedURL: fields: typing.Optional[JSON] = None # type: ignore +@strawberry.type +class MultipartUploadCredentials: + protocol: str + namespace: str + path: str + access_key_id: str + secret_access_key: str + session_token: str + expiration: str + + # Define graphQL input types so we can pass a "file" JSON to mutations. # Keep them separate so we can control which fields are required. @strawberry.input() @@ -71,7 +89,7 @@ def download_link( # ------------------------------------------------------------------------------ -# Mutations +# Utilities # ------------------------------------------------------------------------------ @@ -91,6 +109,49 @@ async def validate_file( await session.commit() +def generate_multipart_upload_token( + new_file: db.File, + expiration: int = 3600, + sts_client: STSClient = Depends(get_sts_client), +): + policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowSampleUploads", + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:CreateMultipartUpload", + "s3:AbortMultipartUpload", + "s3:ListMultipartUploadParts", + ], + "Resource": f"arn:aws:s3:::{new_file.namespace}/{new_file.path}", + } + ], + } + + # Generate an STS token to allow users to + token_name = f"file-upload-token-{uuid6.uuid7()}" + creds = sts_client.get_federation_token(Name=token_name, Policy=json.dumps(policy), DurationSeconds=expiration) + + return MultipartUploadCredentials( + protocol="S3", + namespace=new_file.namespace, + path=new_file.path, + access_key_id=creds["Credentials"]["AccessKeyId"], + secret_access_key=creds["Credentials"]["SecretAccessKey"], + session_token=creds["Credentials"]["SessionToken"], + expiration=creds["Credentials"]["Expiration"].isoformat(), + ) + + +# ------------------------------------------------------------------------------ +# Mutations +# ------------------------------------------------------------------------------ + + @strawberry.mutation(extensions=[DependencyExtension()]) async def mark_upload_complete( file_id: uuid.UUID, @@ -123,9 +184,9 @@ async def create_file( settings: APISettings = Depends(get_settings), ) -> db.File: new_file = await create_or_upload_file( - entity_id, entity_field_name, file, -1, session, cerbos_client, principal, s3_client, settings + entity_id, entity_field_name, file, -1, session, cerbos_client, principal, s3_client, None, settings ) - assert isinstance(new_file, db.File) # this is to reassure mypy that we are in fact returning db.File + assert isinstance(new_file, db.File) # reassure mypy that we're returning the right type return new_file @@ -139,13 +200,23 @@ async def upload_file( cerbos_client: CerbosClient = Depends(get_cerbos_client), principal: Principal = Depends(require_auth_principal), s3_client: S3Client = Depends(get_s3_client), + sts_client: STSClient = Depends(get_sts_client), settings: APISettings = Depends(get_settings), -) -> SignedURL: - new_file = await create_or_upload_file( - entity_id, entity_field_name, file, expiration, session, cerbos_client, principal, s3_client, settings +) -> MultipartUploadCredentials: + credentials = await create_or_upload_file( + entity_id, + entity_field_name, + file, + expiration, + session, + cerbos_client, + principal, + s3_client, + sts_client, + settings, ) - assert isinstance(new_file, SignedURL) # this is to reassure mypy that we are in fact returning a SignedURL - return new_file + assert isinstance(credentials, MultipartUploadCredentials) # reassure mypy that we're returning the right type + return credentials async def create_or_upload_file( @@ -157,8 +228,9 @@ async def create_or_upload_file( cerbos_client: CerbosClient = Depends(get_cerbos_client), principal: Principal = Depends(require_auth_principal), s3_client: S3Client = Depends(get_s3_client), + sts_client: STSClient = Depends(get_sts_client), settings: APISettings = Depends(get_settings), -) -> db.File | SignedURL: +) -> db.File | MultipartUploadCredentials: # Basic validation if "/" in file.name: raise Exception("File name should not contain /") @@ -210,9 +282,6 @@ async def create_or_upload_file( await validate_file(new_file, session, s3_client) return new_file - # If new file, create a signed URL + # If new file, create an STS token for multipart upload else: - response = s3_client.generate_presigned_post(Bucket=new_file.namespace, Key=new_file.path, ExpiresIn=expiration) - return SignedURL( - url=response["url"], fields=response["fields"], protocol="https", method="POST", expiration=expiration - ) + return generate_multipart_upload_token(new_file, expiration, sts_client) diff --git a/entities/api/main.py b/entities/api/main.py index 34f8b4fa..46933494 100644 --- a/entities/api/main.py +++ b/entities/api/main.py @@ -18,7 +18,7 @@ from platformics.database.connect import AsyncDB from strawberry.fastapi import GraphQLRouter from api.strawberry import strawberry_sqlalchemy_mapper -from api.files import File, SignedURL, mark_upload_complete, create_file, upload_file +from api.files import File, MultipartUploadCredentials, mark_upload_complete, create_file, upload_file ###################### # Strawberry-GraphQL # @@ -75,7 +75,7 @@ class Mutation: # File management create_file: File = create_file - upload_file: SignedURL = upload_file + upload_file: MultipartUploadCredentials = upload_file mark_upload_complete: File = mark_upload_complete diff --git a/platformics/api/core/deps.py b/platformics/api/core/deps.py index 49e4804b..0f66d62a 100644 --- a/platformics/api/core/deps.py +++ b/platformics/api/core/deps.py @@ -94,3 +94,12 @@ def get_s3_client( endpoint_url=settings.BOTO_ENDPOINT_URL, config=Config(signature_version="s3v4"), ) + +def get_sts_client( + settings: APISettings = Depends(get_settings), +): + return boto3.client( + "sts", + region_name=settings.AWS_REGION, + endpoint_url=settings.BOTO_ENDPOINT_URL + ) From c964974ba6555a98ca35a3796574609b50c36c2b Mon Sep 17 00:00:00 2001 From: Robert Aboukhalil Date: Fri, 6 Oct 2023 09:40:53 -0700 Subject: [PATCH 2/5] Small fixes --- entities/api/files.py | 1 + entities/poetry.lock | 45 ++++++++++++++++++++++++++++-------- entities/pyproject.toml | 2 +- platformics/api/core/deps.py | 3 ++- workflows/docker-compose.yml | 1 + 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/entities/api/files.py b/entities/api/files.py index 4951b4ee..4c433229 100644 --- a/entities/api/files.py +++ b/entities/api/files.py @@ -6,6 +6,7 @@ import uuid6 from fastapi import Depends from mypy_boto3_s3.client import S3Client +from mypy_boto3_sts.client import STSClient from platformics.api.core.deps import get_s3_client from platformics.api.core.strawberry_extensions import DependencyExtension from api.strawberry import strawberry_sqlalchemy_mapper diff --git a/entities/poetry.lock b/entities/poetry.lock index 17c7d540..f08e5371 100644 --- a/entities/poetry.lock +++ b/entities/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. [[package]] name = "alembic" @@ -204,18 +204,19 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "boto3-stubs" -version = "1.28.57" -description = "Type annotations for boto3 1.28.57 generated with mypy-boto3-builder 7.19.0" +version = "1.28.61" +description = "Type annotations for boto3 1.28.61 generated with mypy-boto3-builder 7.19.0" optional = false python-versions = ">=3.7" files = [ - {file = "boto3-stubs-1.28.57.tar.gz", hash = "sha256:61cd792792c2a16d70801d187ebed705c39962a70e90b5dc0f33d04001fb39ae"}, - {file = "boto3_stubs-1.28.57-py3-none-any.whl", hash = "sha256:b389a693e33d75ed19d38b4a2ba99d25f4f2045a3136f816d7fec4d27a71ddb4"}, + {file = "boto3-stubs-1.28.61.tar.gz", hash = "sha256:326499fc1e60a3047aded687ec9edee8429d6bb3597443dc18a75ae064e91a42"}, + {file = "boto3_stubs-1.28.61-py3-none-any.whl", hash = "sha256:1d0755bcea5502533bf41961659a6c8573b4f2d7d283859a0d7cc8bf8a2b7ed4"}, ] [package.dependencies] botocore-stubs = "*" mypy-boto3-s3 = {version = ">=1.28.0,<1.29.0", optional = true, markers = "extra == \"s3\""} +mypy-boto3-sts = {version = ">=1.28.0,<1.29.0", optional = true, markers = "extra == \"sts\""} types-s3transfer = "*" typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} @@ -225,7 +226,7 @@ account = ["mypy-boto3-account (>=1.28.0,<1.29.0)"] acm = ["mypy-boto3-acm (>=1.28.0,<1.29.0)"] acm-pca = ["mypy-boto3-acm-pca (>=1.28.0,<1.29.0)"] alexaforbusiness = ["mypy-boto3-alexaforbusiness (>=1.28.0,<1.29.0)"] -all = ["mypy-boto3-accessanalyzer (>=1.28.0,<1.29.0)", "mypy-boto3-account (>=1.28.0,<1.29.0)", "mypy-boto3-acm (>=1.28.0,<1.29.0)", "mypy-boto3-acm-pca (>=1.28.0,<1.29.0)", "mypy-boto3-alexaforbusiness (>=1.28.0,<1.29.0)", "mypy-boto3-amp (>=1.28.0,<1.29.0)", "mypy-boto3-amplify (>=1.28.0,<1.29.0)", "mypy-boto3-amplifybackend (>=1.28.0,<1.29.0)", "mypy-boto3-amplifyuibuilder (>=1.28.0,<1.29.0)", "mypy-boto3-apigateway (>=1.28.0,<1.29.0)", "mypy-boto3-apigatewaymanagementapi (>=1.28.0,<1.29.0)", "mypy-boto3-apigatewayv2 (>=1.28.0,<1.29.0)", "mypy-boto3-appconfig (>=1.28.0,<1.29.0)", "mypy-boto3-appconfigdata (>=1.28.0,<1.29.0)", "mypy-boto3-appfabric (>=1.28.0,<1.29.0)", "mypy-boto3-appflow (>=1.28.0,<1.29.0)", "mypy-boto3-appintegrations (>=1.28.0,<1.29.0)", "mypy-boto3-application-autoscaling (>=1.28.0,<1.29.0)", "mypy-boto3-application-insights (>=1.28.0,<1.29.0)", "mypy-boto3-applicationcostprofiler (>=1.28.0,<1.29.0)", "mypy-boto3-appmesh (>=1.28.0,<1.29.0)", "mypy-boto3-apprunner (>=1.28.0,<1.29.0)", "mypy-boto3-appstream (>=1.28.0,<1.29.0)", "mypy-boto3-appsync (>=1.28.0,<1.29.0)", "mypy-boto3-arc-zonal-shift (>=1.28.0,<1.29.0)", "mypy-boto3-athena (>=1.28.0,<1.29.0)", "mypy-boto3-auditmanager (>=1.28.0,<1.29.0)", "mypy-boto3-autoscaling (>=1.28.0,<1.29.0)", "mypy-boto3-autoscaling-plans (>=1.28.0,<1.29.0)", "mypy-boto3-backup (>=1.28.0,<1.29.0)", "mypy-boto3-backup-gateway (>=1.28.0,<1.29.0)", "mypy-boto3-backupstorage (>=1.28.0,<1.29.0)", "mypy-boto3-batch (>=1.28.0,<1.29.0)", "mypy-boto3-bedrock (>=1.28.0,<1.29.0)", "mypy-boto3-bedrock-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-billingconductor (>=1.28.0,<1.29.0)", "mypy-boto3-braket (>=1.28.0,<1.29.0)", "mypy-boto3-budgets (>=1.28.0,<1.29.0)", "mypy-boto3-ce (>=1.28.0,<1.29.0)", "mypy-boto3-chime (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-identity (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-meetings (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-messaging (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-voice (>=1.28.0,<1.29.0)", "mypy-boto3-cleanrooms (>=1.28.0,<1.29.0)", "mypy-boto3-cloud9 (>=1.28.0,<1.29.0)", "mypy-boto3-cloudcontrol (>=1.28.0,<1.29.0)", "mypy-boto3-clouddirectory (>=1.28.0,<1.29.0)", "mypy-boto3-cloudformation (>=1.28.0,<1.29.0)", "mypy-boto3-cloudfront (>=1.28.0,<1.29.0)", "mypy-boto3-cloudhsm (>=1.28.0,<1.29.0)", "mypy-boto3-cloudhsmv2 (>=1.28.0,<1.29.0)", "mypy-boto3-cloudsearch (>=1.28.0,<1.29.0)", "mypy-boto3-cloudsearchdomain (>=1.28.0,<1.29.0)", "mypy-boto3-cloudtrail (>=1.28.0,<1.29.0)", "mypy-boto3-cloudtrail-data (>=1.28.0,<1.29.0)", "mypy-boto3-cloudwatch (>=1.28.0,<1.29.0)", "mypy-boto3-codeartifact (>=1.28.0,<1.29.0)", "mypy-boto3-codebuild (>=1.28.0,<1.29.0)", "mypy-boto3-codecatalyst (>=1.28.0,<1.29.0)", "mypy-boto3-codecommit (>=1.28.0,<1.29.0)", "mypy-boto3-codedeploy (>=1.28.0,<1.29.0)", "mypy-boto3-codeguru-reviewer (>=1.28.0,<1.29.0)", "mypy-boto3-codeguru-security (>=1.28.0,<1.29.0)", "mypy-boto3-codeguruprofiler (>=1.28.0,<1.29.0)", "mypy-boto3-codepipeline (>=1.28.0,<1.29.0)", "mypy-boto3-codestar (>=1.28.0,<1.29.0)", "mypy-boto3-codestar-connections (>=1.28.0,<1.29.0)", "mypy-boto3-codestar-notifications (>=1.28.0,<1.29.0)", "mypy-boto3-cognito-identity (>=1.28.0,<1.29.0)", "mypy-boto3-cognito-idp (>=1.28.0,<1.29.0)", "mypy-boto3-cognito-sync (>=1.28.0,<1.29.0)", "mypy-boto3-comprehend (>=1.28.0,<1.29.0)", "mypy-boto3-comprehendmedical (>=1.28.0,<1.29.0)", "mypy-boto3-compute-optimizer (>=1.28.0,<1.29.0)", "mypy-boto3-config (>=1.28.0,<1.29.0)", "mypy-boto3-connect (>=1.28.0,<1.29.0)", "mypy-boto3-connect-contact-lens (>=1.28.0,<1.29.0)", "mypy-boto3-connectcampaigns (>=1.28.0,<1.29.0)", "mypy-boto3-connectcases (>=1.28.0,<1.29.0)", "mypy-boto3-connectparticipant (>=1.28.0,<1.29.0)", "mypy-boto3-controltower (>=1.28.0,<1.29.0)", "mypy-boto3-cur (>=1.28.0,<1.29.0)", "mypy-boto3-customer-profiles (>=1.28.0,<1.29.0)", "mypy-boto3-databrew (>=1.28.0,<1.29.0)", "mypy-boto3-dataexchange (>=1.28.0,<1.29.0)", "mypy-boto3-datapipeline (>=1.28.0,<1.29.0)", "mypy-boto3-datasync (>=1.28.0,<1.29.0)", "mypy-boto3-dax (>=1.28.0,<1.29.0)", "mypy-boto3-detective (>=1.28.0,<1.29.0)", "mypy-boto3-devicefarm (>=1.28.0,<1.29.0)", "mypy-boto3-devops-guru (>=1.28.0,<1.29.0)", "mypy-boto3-directconnect (>=1.28.0,<1.29.0)", "mypy-boto3-discovery (>=1.28.0,<1.29.0)", "mypy-boto3-dlm (>=1.28.0,<1.29.0)", "mypy-boto3-dms (>=1.28.0,<1.29.0)", "mypy-boto3-docdb (>=1.28.0,<1.29.0)", "mypy-boto3-docdb-elastic (>=1.28.0,<1.29.0)", "mypy-boto3-drs (>=1.28.0,<1.29.0)", "mypy-boto3-ds (>=1.28.0,<1.29.0)", "mypy-boto3-dynamodb (>=1.28.0,<1.29.0)", "mypy-boto3-dynamodbstreams (>=1.28.0,<1.29.0)", "mypy-boto3-ebs (>=1.28.0,<1.29.0)", "mypy-boto3-ec2 (>=1.28.0,<1.29.0)", "mypy-boto3-ec2-instance-connect (>=1.28.0,<1.29.0)", "mypy-boto3-ecr (>=1.28.0,<1.29.0)", "mypy-boto3-ecr-public (>=1.28.0,<1.29.0)", "mypy-boto3-ecs (>=1.28.0,<1.29.0)", "mypy-boto3-efs (>=1.28.0,<1.29.0)", "mypy-boto3-eks (>=1.28.0,<1.29.0)", "mypy-boto3-elastic-inference (>=1.28.0,<1.29.0)", "mypy-boto3-elasticache (>=1.28.0,<1.29.0)", "mypy-boto3-elasticbeanstalk (>=1.28.0,<1.29.0)", "mypy-boto3-elastictranscoder (>=1.28.0,<1.29.0)", "mypy-boto3-elb (>=1.28.0,<1.29.0)", "mypy-boto3-elbv2 (>=1.28.0,<1.29.0)", "mypy-boto3-emr (>=1.28.0,<1.29.0)", "mypy-boto3-emr-containers (>=1.28.0,<1.29.0)", "mypy-boto3-emr-serverless (>=1.28.0,<1.29.0)", "mypy-boto3-entityresolution (>=1.28.0,<1.29.0)", "mypy-boto3-es (>=1.28.0,<1.29.0)", "mypy-boto3-events (>=1.28.0,<1.29.0)", "mypy-boto3-evidently (>=1.28.0,<1.29.0)", "mypy-boto3-finspace (>=1.28.0,<1.29.0)", "mypy-boto3-finspace-data (>=1.28.0,<1.29.0)", "mypy-boto3-firehose (>=1.28.0,<1.29.0)", "mypy-boto3-fis (>=1.28.0,<1.29.0)", "mypy-boto3-fms (>=1.28.0,<1.29.0)", "mypy-boto3-forecast (>=1.28.0,<1.29.0)", "mypy-boto3-forecastquery (>=1.28.0,<1.29.0)", "mypy-boto3-frauddetector (>=1.28.0,<1.29.0)", "mypy-boto3-fsx (>=1.28.0,<1.29.0)", "mypy-boto3-gamelift (>=1.28.0,<1.29.0)", "mypy-boto3-gamesparks (>=1.28.0,<1.29.0)", "mypy-boto3-glacier (>=1.28.0,<1.29.0)", "mypy-boto3-globalaccelerator (>=1.28.0,<1.29.0)", "mypy-boto3-glue (>=1.28.0,<1.29.0)", "mypy-boto3-grafana (>=1.28.0,<1.29.0)", "mypy-boto3-greengrass (>=1.28.0,<1.29.0)", "mypy-boto3-greengrassv2 (>=1.28.0,<1.29.0)", "mypy-boto3-groundstation (>=1.28.0,<1.29.0)", "mypy-boto3-guardduty (>=1.28.0,<1.29.0)", "mypy-boto3-health (>=1.28.0,<1.29.0)", "mypy-boto3-healthlake (>=1.28.0,<1.29.0)", "mypy-boto3-honeycode (>=1.28.0,<1.29.0)", "mypy-boto3-iam (>=1.28.0,<1.29.0)", "mypy-boto3-identitystore (>=1.28.0,<1.29.0)", "mypy-boto3-imagebuilder (>=1.28.0,<1.29.0)", "mypy-boto3-importexport (>=1.28.0,<1.29.0)", "mypy-boto3-inspector (>=1.28.0,<1.29.0)", "mypy-boto3-inspector2 (>=1.28.0,<1.29.0)", "mypy-boto3-internetmonitor (>=1.28.0,<1.29.0)", "mypy-boto3-iot (>=1.28.0,<1.29.0)", "mypy-boto3-iot-data (>=1.28.0,<1.29.0)", "mypy-boto3-iot-jobs-data (>=1.28.0,<1.29.0)", "mypy-boto3-iot-roborunner (>=1.28.0,<1.29.0)", "mypy-boto3-iot1click-devices (>=1.28.0,<1.29.0)", "mypy-boto3-iot1click-projects (>=1.28.0,<1.29.0)", "mypy-boto3-iotanalytics (>=1.28.0,<1.29.0)", "mypy-boto3-iotdeviceadvisor (>=1.28.0,<1.29.0)", "mypy-boto3-iotevents (>=1.28.0,<1.29.0)", "mypy-boto3-iotevents-data (>=1.28.0,<1.29.0)", "mypy-boto3-iotfleethub (>=1.28.0,<1.29.0)", "mypy-boto3-iotfleetwise (>=1.28.0,<1.29.0)", "mypy-boto3-iotsecuretunneling (>=1.28.0,<1.29.0)", "mypy-boto3-iotsitewise (>=1.28.0,<1.29.0)", "mypy-boto3-iotthingsgraph (>=1.28.0,<1.29.0)", "mypy-boto3-iottwinmaker (>=1.28.0,<1.29.0)", "mypy-boto3-iotwireless (>=1.28.0,<1.29.0)", "mypy-boto3-ivs (>=1.28.0,<1.29.0)", "mypy-boto3-ivs-realtime (>=1.28.0,<1.29.0)", "mypy-boto3-ivschat (>=1.28.0,<1.29.0)", "mypy-boto3-kafka (>=1.28.0,<1.29.0)", "mypy-boto3-kafkaconnect (>=1.28.0,<1.29.0)", "mypy-boto3-kendra (>=1.28.0,<1.29.0)", "mypy-boto3-kendra-ranking (>=1.28.0,<1.29.0)", "mypy-boto3-keyspaces (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-archived-media (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-media (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-signaling (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.28.0,<1.29.0)", "mypy-boto3-kinesisanalytics (>=1.28.0,<1.29.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.28.0,<1.29.0)", "mypy-boto3-kinesisvideo (>=1.28.0,<1.29.0)", "mypy-boto3-kms (>=1.28.0,<1.29.0)", "mypy-boto3-lakeformation (>=1.28.0,<1.29.0)", "mypy-boto3-lambda (>=1.28.0,<1.29.0)", "mypy-boto3-lex-models (>=1.28.0,<1.29.0)", "mypy-boto3-lex-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-lexv2-models (>=1.28.0,<1.29.0)", "mypy-boto3-lexv2-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-license-manager (>=1.28.0,<1.29.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.28.0,<1.29.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.28.0,<1.29.0)", "mypy-boto3-lightsail (>=1.28.0,<1.29.0)", "mypy-boto3-location (>=1.28.0,<1.29.0)", "mypy-boto3-logs (>=1.28.0,<1.29.0)", "mypy-boto3-lookoutequipment (>=1.28.0,<1.29.0)", "mypy-boto3-lookoutmetrics (>=1.28.0,<1.29.0)", "mypy-boto3-lookoutvision (>=1.28.0,<1.29.0)", "mypy-boto3-m2 (>=1.28.0,<1.29.0)", "mypy-boto3-machinelearning (>=1.28.0,<1.29.0)", "mypy-boto3-macie (>=1.28.0,<1.29.0)", "mypy-boto3-macie2 (>=1.28.0,<1.29.0)", "mypy-boto3-managedblockchain (>=1.28.0,<1.29.0)", "mypy-boto3-managedblockchain-query (>=1.28.0,<1.29.0)", "mypy-boto3-marketplace-catalog (>=1.28.0,<1.29.0)", "mypy-boto3-marketplace-entitlement (>=1.28.0,<1.29.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.28.0,<1.29.0)", "mypy-boto3-mediaconnect (>=1.28.0,<1.29.0)", "mypy-boto3-mediaconvert (>=1.28.0,<1.29.0)", "mypy-boto3-medialive (>=1.28.0,<1.29.0)", "mypy-boto3-mediapackage (>=1.28.0,<1.29.0)", "mypy-boto3-mediapackage-vod (>=1.28.0,<1.29.0)", "mypy-boto3-mediapackagev2 (>=1.28.0,<1.29.0)", "mypy-boto3-mediastore (>=1.28.0,<1.29.0)", "mypy-boto3-mediastore-data (>=1.28.0,<1.29.0)", "mypy-boto3-mediatailor (>=1.28.0,<1.29.0)", "mypy-boto3-medical-imaging (>=1.28.0,<1.29.0)", "mypy-boto3-memorydb (>=1.28.0,<1.29.0)", "mypy-boto3-meteringmarketplace (>=1.28.0,<1.29.0)", "mypy-boto3-mgh (>=1.28.0,<1.29.0)", "mypy-boto3-mgn (>=1.28.0,<1.29.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.28.0,<1.29.0)", "mypy-boto3-migrationhub-config (>=1.28.0,<1.29.0)", "mypy-boto3-migrationhuborchestrator (>=1.28.0,<1.29.0)", "mypy-boto3-migrationhubstrategy (>=1.28.0,<1.29.0)", "mypy-boto3-mobile (>=1.28.0,<1.29.0)", "mypy-boto3-mq (>=1.28.0,<1.29.0)", "mypy-boto3-mturk (>=1.28.0,<1.29.0)", "mypy-boto3-mwaa (>=1.28.0,<1.29.0)", "mypy-boto3-neptune (>=1.28.0,<1.29.0)", "mypy-boto3-neptunedata (>=1.28.0,<1.29.0)", "mypy-boto3-network-firewall (>=1.28.0,<1.29.0)", "mypy-boto3-networkmanager (>=1.28.0,<1.29.0)", "mypy-boto3-nimble (>=1.28.0,<1.29.0)", "mypy-boto3-oam (>=1.28.0,<1.29.0)", "mypy-boto3-omics (>=1.28.0,<1.29.0)", "mypy-boto3-opensearch (>=1.28.0,<1.29.0)", "mypy-boto3-opensearchserverless (>=1.28.0,<1.29.0)", "mypy-boto3-opsworks (>=1.28.0,<1.29.0)", "mypy-boto3-opsworkscm (>=1.28.0,<1.29.0)", "mypy-boto3-organizations (>=1.28.0,<1.29.0)", "mypy-boto3-osis (>=1.28.0,<1.29.0)", "mypy-boto3-outposts (>=1.28.0,<1.29.0)", "mypy-boto3-panorama (>=1.28.0,<1.29.0)", "mypy-boto3-payment-cryptography (>=1.28.0,<1.29.0)", "mypy-boto3-payment-cryptography-data (>=1.28.0,<1.29.0)", "mypy-boto3-pca-connector-ad (>=1.28.0,<1.29.0)", "mypy-boto3-personalize (>=1.28.0,<1.29.0)", "mypy-boto3-personalize-events (>=1.28.0,<1.29.0)", "mypy-boto3-personalize-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-pi (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint-email (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint-sms-voice (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.28.0,<1.29.0)", "mypy-boto3-pipes (>=1.28.0,<1.29.0)", "mypy-boto3-polly (>=1.28.0,<1.29.0)", "mypy-boto3-pricing (>=1.28.0,<1.29.0)", "mypy-boto3-privatenetworks (>=1.28.0,<1.29.0)", "mypy-boto3-proton (>=1.28.0,<1.29.0)", "mypy-boto3-qldb (>=1.28.0,<1.29.0)", "mypy-boto3-qldb-session (>=1.28.0,<1.29.0)", "mypy-boto3-quicksight (>=1.28.0,<1.29.0)", "mypy-boto3-ram (>=1.28.0,<1.29.0)", "mypy-boto3-rbin (>=1.28.0,<1.29.0)", "mypy-boto3-rds (>=1.28.0,<1.29.0)", "mypy-boto3-rds-data (>=1.28.0,<1.29.0)", "mypy-boto3-redshift (>=1.28.0,<1.29.0)", "mypy-boto3-redshift-data (>=1.28.0,<1.29.0)", "mypy-boto3-redshift-serverless (>=1.28.0,<1.29.0)", "mypy-boto3-rekognition (>=1.28.0,<1.29.0)", "mypy-boto3-resiliencehub (>=1.28.0,<1.29.0)", "mypy-boto3-resource-explorer-2 (>=1.28.0,<1.29.0)", "mypy-boto3-resource-groups (>=1.28.0,<1.29.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.28.0,<1.29.0)", "mypy-boto3-robomaker (>=1.28.0,<1.29.0)", "mypy-boto3-rolesanywhere (>=1.28.0,<1.29.0)", "mypy-boto3-route53 (>=1.28.0,<1.29.0)", "mypy-boto3-route53-recovery-cluster (>=1.28.0,<1.29.0)", "mypy-boto3-route53-recovery-control-config (>=1.28.0,<1.29.0)", "mypy-boto3-route53-recovery-readiness (>=1.28.0,<1.29.0)", "mypy-boto3-route53domains (>=1.28.0,<1.29.0)", "mypy-boto3-route53resolver (>=1.28.0,<1.29.0)", "mypy-boto3-rum (>=1.28.0,<1.29.0)", "mypy-boto3-s3 (>=1.28.0,<1.29.0)", "mypy-boto3-s3control (>=1.28.0,<1.29.0)", "mypy-boto3-s3outposts (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-edge (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-geospatial (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-metrics (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-savingsplans (>=1.28.0,<1.29.0)", "mypy-boto3-scheduler (>=1.28.0,<1.29.0)", "mypy-boto3-schemas (>=1.28.0,<1.29.0)", "mypy-boto3-sdb (>=1.28.0,<1.29.0)", "mypy-boto3-secretsmanager (>=1.28.0,<1.29.0)", "mypy-boto3-securityhub (>=1.28.0,<1.29.0)", "mypy-boto3-securitylake (>=1.28.0,<1.29.0)", "mypy-boto3-serverlessrepo (>=1.28.0,<1.29.0)", "mypy-boto3-service-quotas (>=1.28.0,<1.29.0)", "mypy-boto3-servicecatalog (>=1.28.0,<1.29.0)", "mypy-boto3-servicecatalog-appregistry (>=1.28.0,<1.29.0)", "mypy-boto3-servicediscovery (>=1.28.0,<1.29.0)", "mypy-boto3-ses (>=1.28.0,<1.29.0)", "mypy-boto3-sesv2 (>=1.28.0,<1.29.0)", "mypy-boto3-shield (>=1.28.0,<1.29.0)", "mypy-boto3-signer (>=1.28.0,<1.29.0)", "mypy-boto3-simspaceweaver (>=1.28.0,<1.29.0)", "mypy-boto3-sms (>=1.28.0,<1.29.0)", "mypy-boto3-sms-voice (>=1.28.0,<1.29.0)", "mypy-boto3-snow-device-management (>=1.28.0,<1.29.0)", "mypy-boto3-snowball (>=1.28.0,<1.29.0)", "mypy-boto3-sns (>=1.28.0,<1.29.0)", "mypy-boto3-sqs (>=1.28.0,<1.29.0)", "mypy-boto3-ssm (>=1.28.0,<1.29.0)", "mypy-boto3-ssm-contacts (>=1.28.0,<1.29.0)", "mypy-boto3-ssm-incidents (>=1.28.0,<1.29.0)", "mypy-boto3-ssm-sap (>=1.28.0,<1.29.0)", "mypy-boto3-sso (>=1.28.0,<1.29.0)", "mypy-boto3-sso-admin (>=1.28.0,<1.29.0)", "mypy-boto3-sso-oidc (>=1.28.0,<1.29.0)", "mypy-boto3-stepfunctions (>=1.28.0,<1.29.0)", "mypy-boto3-storagegateway (>=1.28.0,<1.29.0)", "mypy-boto3-sts (>=1.28.0,<1.29.0)", "mypy-boto3-support (>=1.28.0,<1.29.0)", "mypy-boto3-support-app (>=1.28.0,<1.29.0)", "mypy-boto3-swf (>=1.28.0,<1.29.0)", "mypy-boto3-synthetics (>=1.28.0,<1.29.0)", "mypy-boto3-textract (>=1.28.0,<1.29.0)", "mypy-boto3-timestream-query (>=1.28.0,<1.29.0)", "mypy-boto3-timestream-write (>=1.28.0,<1.29.0)", "mypy-boto3-tnb (>=1.28.0,<1.29.0)", "mypy-boto3-transcribe (>=1.28.0,<1.29.0)", "mypy-boto3-transfer (>=1.28.0,<1.29.0)", "mypy-boto3-translate (>=1.28.0,<1.29.0)", "mypy-boto3-verifiedpermissions (>=1.28.0,<1.29.0)", "mypy-boto3-voice-id (>=1.28.0,<1.29.0)", "mypy-boto3-vpc-lattice (>=1.28.0,<1.29.0)", "mypy-boto3-waf (>=1.28.0,<1.29.0)", "mypy-boto3-waf-regional (>=1.28.0,<1.29.0)", "mypy-boto3-wafv2 (>=1.28.0,<1.29.0)", "mypy-boto3-wellarchitected (>=1.28.0,<1.29.0)", "mypy-boto3-wisdom (>=1.28.0,<1.29.0)", "mypy-boto3-workdocs (>=1.28.0,<1.29.0)", "mypy-boto3-worklink (>=1.28.0,<1.29.0)", "mypy-boto3-workmail (>=1.28.0,<1.29.0)", "mypy-boto3-workmailmessageflow (>=1.28.0,<1.29.0)", "mypy-boto3-workspaces (>=1.28.0,<1.29.0)", "mypy-boto3-workspaces-web (>=1.28.0,<1.29.0)", "mypy-boto3-xray (>=1.28.0,<1.29.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.28.0,<1.29.0)", "mypy-boto3-account (>=1.28.0,<1.29.0)", "mypy-boto3-acm (>=1.28.0,<1.29.0)", "mypy-boto3-acm-pca (>=1.28.0,<1.29.0)", "mypy-boto3-alexaforbusiness (>=1.28.0,<1.29.0)", "mypy-boto3-amp (>=1.28.0,<1.29.0)", "mypy-boto3-amplify (>=1.28.0,<1.29.0)", "mypy-boto3-amplifybackend (>=1.28.0,<1.29.0)", "mypy-boto3-amplifyuibuilder (>=1.28.0,<1.29.0)", "mypy-boto3-apigateway (>=1.28.0,<1.29.0)", "mypy-boto3-apigatewaymanagementapi (>=1.28.0,<1.29.0)", "mypy-boto3-apigatewayv2 (>=1.28.0,<1.29.0)", "mypy-boto3-appconfig (>=1.28.0,<1.29.0)", "mypy-boto3-appconfigdata (>=1.28.0,<1.29.0)", "mypy-boto3-appfabric (>=1.28.0,<1.29.0)", "mypy-boto3-appflow (>=1.28.0,<1.29.0)", "mypy-boto3-appintegrations (>=1.28.0,<1.29.0)", "mypy-boto3-application-autoscaling (>=1.28.0,<1.29.0)", "mypy-boto3-application-insights (>=1.28.0,<1.29.0)", "mypy-boto3-applicationcostprofiler (>=1.28.0,<1.29.0)", "mypy-boto3-appmesh (>=1.28.0,<1.29.0)", "mypy-boto3-apprunner (>=1.28.0,<1.29.0)", "mypy-boto3-appstream (>=1.28.0,<1.29.0)", "mypy-boto3-appsync (>=1.28.0,<1.29.0)", "mypy-boto3-arc-zonal-shift (>=1.28.0,<1.29.0)", "mypy-boto3-athena (>=1.28.0,<1.29.0)", "mypy-boto3-auditmanager (>=1.28.0,<1.29.0)", "mypy-boto3-autoscaling (>=1.28.0,<1.29.0)", "mypy-boto3-autoscaling-plans (>=1.28.0,<1.29.0)", "mypy-boto3-backup (>=1.28.0,<1.29.0)", "mypy-boto3-backup-gateway (>=1.28.0,<1.29.0)", "mypy-boto3-backupstorage (>=1.28.0,<1.29.0)", "mypy-boto3-batch (>=1.28.0,<1.29.0)", "mypy-boto3-bedrock (>=1.28.0,<1.29.0)", "mypy-boto3-bedrock-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-billingconductor (>=1.28.0,<1.29.0)", "mypy-boto3-braket (>=1.28.0,<1.29.0)", "mypy-boto3-budgets (>=1.28.0,<1.29.0)", "mypy-boto3-ce (>=1.28.0,<1.29.0)", "mypy-boto3-chime (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-identity (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-meetings (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-messaging (>=1.28.0,<1.29.0)", "mypy-boto3-chime-sdk-voice (>=1.28.0,<1.29.0)", "mypy-boto3-cleanrooms (>=1.28.0,<1.29.0)", "mypy-boto3-cloud9 (>=1.28.0,<1.29.0)", "mypy-boto3-cloudcontrol (>=1.28.0,<1.29.0)", "mypy-boto3-clouddirectory (>=1.28.0,<1.29.0)", "mypy-boto3-cloudformation (>=1.28.0,<1.29.0)", "mypy-boto3-cloudfront (>=1.28.0,<1.29.0)", "mypy-boto3-cloudhsm (>=1.28.0,<1.29.0)", "mypy-boto3-cloudhsmv2 (>=1.28.0,<1.29.0)", "mypy-boto3-cloudsearch (>=1.28.0,<1.29.0)", "mypy-boto3-cloudsearchdomain (>=1.28.0,<1.29.0)", "mypy-boto3-cloudtrail (>=1.28.0,<1.29.0)", "mypy-boto3-cloudtrail-data (>=1.28.0,<1.29.0)", "mypy-boto3-cloudwatch (>=1.28.0,<1.29.0)", "mypy-boto3-codeartifact (>=1.28.0,<1.29.0)", "mypy-boto3-codebuild (>=1.28.0,<1.29.0)", "mypy-boto3-codecatalyst (>=1.28.0,<1.29.0)", "mypy-boto3-codecommit (>=1.28.0,<1.29.0)", "mypy-boto3-codedeploy (>=1.28.0,<1.29.0)", "mypy-boto3-codeguru-reviewer (>=1.28.0,<1.29.0)", "mypy-boto3-codeguru-security (>=1.28.0,<1.29.0)", "mypy-boto3-codeguruprofiler (>=1.28.0,<1.29.0)", "mypy-boto3-codepipeline (>=1.28.0,<1.29.0)", "mypy-boto3-codestar (>=1.28.0,<1.29.0)", "mypy-boto3-codestar-connections (>=1.28.0,<1.29.0)", "mypy-boto3-codestar-notifications (>=1.28.0,<1.29.0)", "mypy-boto3-cognito-identity (>=1.28.0,<1.29.0)", "mypy-boto3-cognito-idp (>=1.28.0,<1.29.0)", "mypy-boto3-cognito-sync (>=1.28.0,<1.29.0)", "mypy-boto3-comprehend (>=1.28.0,<1.29.0)", "mypy-boto3-comprehendmedical (>=1.28.0,<1.29.0)", "mypy-boto3-compute-optimizer (>=1.28.0,<1.29.0)", "mypy-boto3-config (>=1.28.0,<1.29.0)", "mypy-boto3-connect (>=1.28.0,<1.29.0)", "mypy-boto3-connect-contact-lens (>=1.28.0,<1.29.0)", "mypy-boto3-connectcampaigns (>=1.28.0,<1.29.0)", "mypy-boto3-connectcases (>=1.28.0,<1.29.0)", "mypy-boto3-connectparticipant (>=1.28.0,<1.29.0)", "mypy-boto3-controltower (>=1.28.0,<1.29.0)", "mypy-boto3-cur (>=1.28.0,<1.29.0)", "mypy-boto3-customer-profiles (>=1.28.0,<1.29.0)", "mypy-boto3-databrew (>=1.28.0,<1.29.0)", "mypy-boto3-dataexchange (>=1.28.0,<1.29.0)", "mypy-boto3-datapipeline (>=1.28.0,<1.29.0)", "mypy-boto3-datasync (>=1.28.0,<1.29.0)", "mypy-boto3-datazone (>=1.28.0,<1.29.0)", "mypy-boto3-dax (>=1.28.0,<1.29.0)", "mypy-boto3-detective (>=1.28.0,<1.29.0)", "mypy-boto3-devicefarm (>=1.28.0,<1.29.0)", "mypy-boto3-devops-guru (>=1.28.0,<1.29.0)", "mypy-boto3-directconnect (>=1.28.0,<1.29.0)", "mypy-boto3-discovery (>=1.28.0,<1.29.0)", "mypy-boto3-dlm (>=1.28.0,<1.29.0)", "mypy-boto3-dms (>=1.28.0,<1.29.0)", "mypy-boto3-docdb (>=1.28.0,<1.29.0)", "mypy-boto3-docdb-elastic (>=1.28.0,<1.29.0)", "mypy-boto3-drs (>=1.28.0,<1.29.0)", "mypy-boto3-ds (>=1.28.0,<1.29.0)", "mypy-boto3-dynamodb (>=1.28.0,<1.29.0)", "mypy-boto3-dynamodbstreams (>=1.28.0,<1.29.0)", "mypy-boto3-ebs (>=1.28.0,<1.29.0)", "mypy-boto3-ec2 (>=1.28.0,<1.29.0)", "mypy-boto3-ec2-instance-connect (>=1.28.0,<1.29.0)", "mypy-boto3-ecr (>=1.28.0,<1.29.0)", "mypy-boto3-ecr-public (>=1.28.0,<1.29.0)", "mypy-boto3-ecs (>=1.28.0,<1.29.0)", "mypy-boto3-efs (>=1.28.0,<1.29.0)", "mypy-boto3-eks (>=1.28.0,<1.29.0)", "mypy-boto3-elastic-inference (>=1.28.0,<1.29.0)", "mypy-boto3-elasticache (>=1.28.0,<1.29.0)", "mypy-boto3-elasticbeanstalk (>=1.28.0,<1.29.0)", "mypy-boto3-elastictranscoder (>=1.28.0,<1.29.0)", "mypy-boto3-elb (>=1.28.0,<1.29.0)", "mypy-boto3-elbv2 (>=1.28.0,<1.29.0)", "mypy-boto3-emr (>=1.28.0,<1.29.0)", "mypy-boto3-emr-containers (>=1.28.0,<1.29.0)", "mypy-boto3-emr-serverless (>=1.28.0,<1.29.0)", "mypy-boto3-entityresolution (>=1.28.0,<1.29.0)", "mypy-boto3-es (>=1.28.0,<1.29.0)", "mypy-boto3-events (>=1.28.0,<1.29.0)", "mypy-boto3-evidently (>=1.28.0,<1.29.0)", "mypy-boto3-finspace (>=1.28.0,<1.29.0)", "mypy-boto3-finspace-data (>=1.28.0,<1.29.0)", "mypy-boto3-firehose (>=1.28.0,<1.29.0)", "mypy-boto3-fis (>=1.28.0,<1.29.0)", "mypy-boto3-fms (>=1.28.0,<1.29.0)", "mypy-boto3-forecast (>=1.28.0,<1.29.0)", "mypy-boto3-forecastquery (>=1.28.0,<1.29.0)", "mypy-boto3-frauddetector (>=1.28.0,<1.29.0)", "mypy-boto3-fsx (>=1.28.0,<1.29.0)", "mypy-boto3-gamelift (>=1.28.0,<1.29.0)", "mypy-boto3-gamesparks (>=1.28.0,<1.29.0)", "mypy-boto3-glacier (>=1.28.0,<1.29.0)", "mypy-boto3-globalaccelerator (>=1.28.0,<1.29.0)", "mypy-boto3-glue (>=1.28.0,<1.29.0)", "mypy-boto3-grafana (>=1.28.0,<1.29.0)", "mypy-boto3-greengrass (>=1.28.0,<1.29.0)", "mypy-boto3-greengrassv2 (>=1.28.0,<1.29.0)", "mypy-boto3-groundstation (>=1.28.0,<1.29.0)", "mypy-boto3-guardduty (>=1.28.0,<1.29.0)", "mypy-boto3-health (>=1.28.0,<1.29.0)", "mypy-boto3-healthlake (>=1.28.0,<1.29.0)", "mypy-boto3-honeycode (>=1.28.0,<1.29.0)", "mypy-boto3-iam (>=1.28.0,<1.29.0)", "mypy-boto3-identitystore (>=1.28.0,<1.29.0)", "mypy-boto3-imagebuilder (>=1.28.0,<1.29.0)", "mypy-boto3-importexport (>=1.28.0,<1.29.0)", "mypy-boto3-inspector (>=1.28.0,<1.29.0)", "mypy-boto3-inspector2 (>=1.28.0,<1.29.0)", "mypy-boto3-internetmonitor (>=1.28.0,<1.29.0)", "mypy-boto3-iot (>=1.28.0,<1.29.0)", "mypy-boto3-iot-data (>=1.28.0,<1.29.0)", "mypy-boto3-iot-jobs-data (>=1.28.0,<1.29.0)", "mypy-boto3-iot-roborunner (>=1.28.0,<1.29.0)", "mypy-boto3-iot1click-devices (>=1.28.0,<1.29.0)", "mypy-boto3-iot1click-projects (>=1.28.0,<1.29.0)", "mypy-boto3-iotanalytics (>=1.28.0,<1.29.0)", "mypy-boto3-iotdeviceadvisor (>=1.28.0,<1.29.0)", "mypy-boto3-iotevents (>=1.28.0,<1.29.0)", "mypy-boto3-iotevents-data (>=1.28.0,<1.29.0)", "mypy-boto3-iotfleethub (>=1.28.0,<1.29.0)", "mypy-boto3-iotfleetwise (>=1.28.0,<1.29.0)", "mypy-boto3-iotsecuretunneling (>=1.28.0,<1.29.0)", "mypy-boto3-iotsitewise (>=1.28.0,<1.29.0)", "mypy-boto3-iotthingsgraph (>=1.28.0,<1.29.0)", "mypy-boto3-iottwinmaker (>=1.28.0,<1.29.0)", "mypy-boto3-iotwireless (>=1.28.0,<1.29.0)", "mypy-boto3-ivs (>=1.28.0,<1.29.0)", "mypy-boto3-ivs-realtime (>=1.28.0,<1.29.0)", "mypy-boto3-ivschat (>=1.28.0,<1.29.0)", "mypy-boto3-kafka (>=1.28.0,<1.29.0)", "mypy-boto3-kafkaconnect (>=1.28.0,<1.29.0)", "mypy-boto3-kendra (>=1.28.0,<1.29.0)", "mypy-boto3-kendra-ranking (>=1.28.0,<1.29.0)", "mypy-boto3-keyspaces (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-archived-media (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-media (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-signaling (>=1.28.0,<1.29.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.28.0,<1.29.0)", "mypy-boto3-kinesisanalytics (>=1.28.0,<1.29.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.28.0,<1.29.0)", "mypy-boto3-kinesisvideo (>=1.28.0,<1.29.0)", "mypy-boto3-kms (>=1.28.0,<1.29.0)", "mypy-boto3-lakeformation (>=1.28.0,<1.29.0)", "mypy-boto3-lambda (>=1.28.0,<1.29.0)", "mypy-boto3-lex-models (>=1.28.0,<1.29.0)", "mypy-boto3-lex-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-lexv2-models (>=1.28.0,<1.29.0)", "mypy-boto3-lexv2-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-license-manager (>=1.28.0,<1.29.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.28.0,<1.29.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.28.0,<1.29.0)", "mypy-boto3-lightsail (>=1.28.0,<1.29.0)", "mypy-boto3-location (>=1.28.0,<1.29.0)", "mypy-boto3-logs (>=1.28.0,<1.29.0)", "mypy-boto3-lookoutequipment (>=1.28.0,<1.29.0)", "mypy-boto3-lookoutmetrics (>=1.28.0,<1.29.0)", "mypy-boto3-lookoutvision (>=1.28.0,<1.29.0)", "mypy-boto3-m2 (>=1.28.0,<1.29.0)", "mypy-boto3-machinelearning (>=1.28.0,<1.29.0)", "mypy-boto3-macie (>=1.28.0,<1.29.0)", "mypy-boto3-macie2 (>=1.28.0,<1.29.0)", "mypy-boto3-managedblockchain (>=1.28.0,<1.29.0)", "mypy-boto3-managedblockchain-query (>=1.28.0,<1.29.0)", "mypy-boto3-marketplace-catalog (>=1.28.0,<1.29.0)", "mypy-boto3-marketplace-entitlement (>=1.28.0,<1.29.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.28.0,<1.29.0)", "mypy-boto3-mediaconnect (>=1.28.0,<1.29.0)", "mypy-boto3-mediaconvert (>=1.28.0,<1.29.0)", "mypy-boto3-medialive (>=1.28.0,<1.29.0)", "mypy-boto3-mediapackage (>=1.28.0,<1.29.0)", "mypy-boto3-mediapackage-vod (>=1.28.0,<1.29.0)", "mypy-boto3-mediapackagev2 (>=1.28.0,<1.29.0)", "mypy-boto3-mediastore (>=1.28.0,<1.29.0)", "mypy-boto3-mediastore-data (>=1.28.0,<1.29.0)", "mypy-boto3-mediatailor (>=1.28.0,<1.29.0)", "mypy-boto3-medical-imaging (>=1.28.0,<1.29.0)", "mypy-boto3-memorydb (>=1.28.0,<1.29.0)", "mypy-boto3-meteringmarketplace (>=1.28.0,<1.29.0)", "mypy-boto3-mgh (>=1.28.0,<1.29.0)", "mypy-boto3-mgn (>=1.28.0,<1.29.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.28.0,<1.29.0)", "mypy-boto3-migrationhub-config (>=1.28.0,<1.29.0)", "mypy-boto3-migrationhuborchestrator (>=1.28.0,<1.29.0)", "mypy-boto3-migrationhubstrategy (>=1.28.0,<1.29.0)", "mypy-boto3-mobile (>=1.28.0,<1.29.0)", "mypy-boto3-mq (>=1.28.0,<1.29.0)", "mypy-boto3-mturk (>=1.28.0,<1.29.0)", "mypy-boto3-mwaa (>=1.28.0,<1.29.0)", "mypy-boto3-neptune (>=1.28.0,<1.29.0)", "mypy-boto3-neptunedata (>=1.28.0,<1.29.0)", "mypy-boto3-network-firewall (>=1.28.0,<1.29.0)", "mypy-boto3-networkmanager (>=1.28.0,<1.29.0)", "mypy-boto3-nimble (>=1.28.0,<1.29.0)", "mypy-boto3-oam (>=1.28.0,<1.29.0)", "mypy-boto3-omics (>=1.28.0,<1.29.0)", "mypy-boto3-opensearch (>=1.28.0,<1.29.0)", "mypy-boto3-opensearchserverless (>=1.28.0,<1.29.0)", "mypy-boto3-opsworks (>=1.28.0,<1.29.0)", "mypy-boto3-opsworkscm (>=1.28.0,<1.29.0)", "mypy-boto3-organizations (>=1.28.0,<1.29.0)", "mypy-boto3-osis (>=1.28.0,<1.29.0)", "mypy-boto3-outposts (>=1.28.0,<1.29.0)", "mypy-boto3-panorama (>=1.28.0,<1.29.0)", "mypy-boto3-payment-cryptography (>=1.28.0,<1.29.0)", "mypy-boto3-payment-cryptography-data (>=1.28.0,<1.29.0)", "mypy-boto3-pca-connector-ad (>=1.28.0,<1.29.0)", "mypy-boto3-personalize (>=1.28.0,<1.29.0)", "mypy-boto3-personalize-events (>=1.28.0,<1.29.0)", "mypy-boto3-personalize-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-pi (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint-email (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint-sms-voice (>=1.28.0,<1.29.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.28.0,<1.29.0)", "mypy-boto3-pipes (>=1.28.0,<1.29.0)", "mypy-boto3-polly (>=1.28.0,<1.29.0)", "mypy-boto3-pricing (>=1.28.0,<1.29.0)", "mypy-boto3-privatenetworks (>=1.28.0,<1.29.0)", "mypy-boto3-proton (>=1.28.0,<1.29.0)", "mypy-boto3-qldb (>=1.28.0,<1.29.0)", "mypy-boto3-qldb-session (>=1.28.0,<1.29.0)", "mypy-boto3-quicksight (>=1.28.0,<1.29.0)", "mypy-boto3-ram (>=1.28.0,<1.29.0)", "mypy-boto3-rbin (>=1.28.0,<1.29.0)", "mypy-boto3-rds (>=1.28.0,<1.29.0)", "mypy-boto3-rds-data (>=1.28.0,<1.29.0)", "mypy-boto3-redshift (>=1.28.0,<1.29.0)", "mypy-boto3-redshift-data (>=1.28.0,<1.29.0)", "mypy-boto3-redshift-serverless (>=1.28.0,<1.29.0)", "mypy-boto3-rekognition (>=1.28.0,<1.29.0)", "mypy-boto3-resiliencehub (>=1.28.0,<1.29.0)", "mypy-boto3-resource-explorer-2 (>=1.28.0,<1.29.0)", "mypy-boto3-resource-groups (>=1.28.0,<1.29.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.28.0,<1.29.0)", "mypy-boto3-robomaker (>=1.28.0,<1.29.0)", "mypy-boto3-rolesanywhere (>=1.28.0,<1.29.0)", "mypy-boto3-route53 (>=1.28.0,<1.29.0)", "mypy-boto3-route53-recovery-cluster (>=1.28.0,<1.29.0)", "mypy-boto3-route53-recovery-control-config (>=1.28.0,<1.29.0)", "mypy-boto3-route53-recovery-readiness (>=1.28.0,<1.29.0)", "mypy-boto3-route53domains (>=1.28.0,<1.29.0)", "mypy-boto3-route53resolver (>=1.28.0,<1.29.0)", "mypy-boto3-rum (>=1.28.0,<1.29.0)", "mypy-boto3-s3 (>=1.28.0,<1.29.0)", "mypy-boto3-s3control (>=1.28.0,<1.29.0)", "mypy-boto3-s3outposts (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-edge (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-geospatial (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-metrics (>=1.28.0,<1.29.0)", "mypy-boto3-sagemaker-runtime (>=1.28.0,<1.29.0)", "mypy-boto3-savingsplans (>=1.28.0,<1.29.0)", "mypy-boto3-scheduler (>=1.28.0,<1.29.0)", "mypy-boto3-schemas (>=1.28.0,<1.29.0)", "mypy-boto3-sdb (>=1.28.0,<1.29.0)", "mypy-boto3-secretsmanager (>=1.28.0,<1.29.0)", "mypy-boto3-securityhub (>=1.28.0,<1.29.0)", "mypy-boto3-securitylake (>=1.28.0,<1.29.0)", "mypy-boto3-serverlessrepo (>=1.28.0,<1.29.0)", "mypy-boto3-service-quotas (>=1.28.0,<1.29.0)", "mypy-boto3-servicecatalog (>=1.28.0,<1.29.0)", "mypy-boto3-servicecatalog-appregistry (>=1.28.0,<1.29.0)", "mypy-boto3-servicediscovery (>=1.28.0,<1.29.0)", "mypy-boto3-ses (>=1.28.0,<1.29.0)", "mypy-boto3-sesv2 (>=1.28.0,<1.29.0)", "mypy-boto3-shield (>=1.28.0,<1.29.0)", "mypy-boto3-signer (>=1.28.0,<1.29.0)", "mypy-boto3-simspaceweaver (>=1.28.0,<1.29.0)", "mypy-boto3-sms (>=1.28.0,<1.29.0)", "mypy-boto3-sms-voice (>=1.28.0,<1.29.0)", "mypy-boto3-snow-device-management (>=1.28.0,<1.29.0)", "mypy-boto3-snowball (>=1.28.0,<1.29.0)", "mypy-boto3-sns (>=1.28.0,<1.29.0)", "mypy-boto3-sqs (>=1.28.0,<1.29.0)", "mypy-boto3-ssm (>=1.28.0,<1.29.0)", "mypy-boto3-ssm-contacts (>=1.28.0,<1.29.0)", "mypy-boto3-ssm-incidents (>=1.28.0,<1.29.0)", "mypy-boto3-ssm-sap (>=1.28.0,<1.29.0)", "mypy-boto3-sso (>=1.28.0,<1.29.0)", "mypy-boto3-sso-admin (>=1.28.0,<1.29.0)", "mypy-boto3-sso-oidc (>=1.28.0,<1.29.0)", "mypy-boto3-stepfunctions (>=1.28.0,<1.29.0)", "mypy-boto3-storagegateway (>=1.28.0,<1.29.0)", "mypy-boto3-sts (>=1.28.0,<1.29.0)", "mypy-boto3-support (>=1.28.0,<1.29.0)", "mypy-boto3-support-app (>=1.28.0,<1.29.0)", "mypy-boto3-swf (>=1.28.0,<1.29.0)", "mypy-boto3-synthetics (>=1.28.0,<1.29.0)", "mypy-boto3-textract (>=1.28.0,<1.29.0)", "mypy-boto3-timestream-query (>=1.28.0,<1.29.0)", "mypy-boto3-timestream-write (>=1.28.0,<1.29.0)", "mypy-boto3-tnb (>=1.28.0,<1.29.0)", "mypy-boto3-transcribe (>=1.28.0,<1.29.0)", "mypy-boto3-transfer (>=1.28.0,<1.29.0)", "mypy-boto3-translate (>=1.28.0,<1.29.0)", "mypy-boto3-verifiedpermissions (>=1.28.0,<1.29.0)", "mypy-boto3-voice-id (>=1.28.0,<1.29.0)", "mypy-boto3-vpc-lattice (>=1.28.0,<1.29.0)", "mypy-boto3-waf (>=1.28.0,<1.29.0)", "mypy-boto3-waf-regional (>=1.28.0,<1.29.0)", "mypy-boto3-wafv2 (>=1.28.0,<1.29.0)", "mypy-boto3-wellarchitected (>=1.28.0,<1.29.0)", "mypy-boto3-wisdom (>=1.28.0,<1.29.0)", "mypy-boto3-workdocs (>=1.28.0,<1.29.0)", "mypy-boto3-worklink (>=1.28.0,<1.29.0)", "mypy-boto3-workmail (>=1.28.0,<1.29.0)", "mypy-boto3-workmailmessageflow (>=1.28.0,<1.29.0)", "mypy-boto3-workspaces (>=1.28.0,<1.29.0)", "mypy-boto3-workspaces-web (>=1.28.0,<1.29.0)", "mypy-boto3-xray (>=1.28.0,<1.29.0)"] amp = ["mypy-boto3-amp (>=1.28.0,<1.29.0)"] amplify = ["mypy-boto3-amplify (>=1.28.0,<1.29.0)"] amplifybackend = ["mypy-boto3-amplifybackend (>=1.28.0,<1.29.0)"] @@ -257,7 +258,7 @@ batch = ["mypy-boto3-batch (>=1.28.0,<1.29.0)"] bedrock = ["mypy-boto3-bedrock (>=1.28.0,<1.29.0)"] bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.28.0,<1.29.0)"] billingconductor = ["mypy-boto3-billingconductor (>=1.28.0,<1.29.0)"] -boto3 = ["boto3 (==1.28.57)", "botocore (==1.31.57)"] +boto3 = ["boto3 (==1.28.61)", "botocore (==1.31.61)"] braket = ["mypy-boto3-braket (>=1.28.0,<1.29.0)"] budgets = ["mypy-boto3-budgets (>=1.28.0,<1.29.0)"] ce = ["mypy-boto3-ce (>=1.28.0,<1.29.0)"] @@ -311,6 +312,7 @@ databrew = ["mypy-boto3-databrew (>=1.28.0,<1.29.0)"] dataexchange = ["mypy-boto3-dataexchange (>=1.28.0,<1.29.0)"] datapipeline = ["mypy-boto3-datapipeline (>=1.28.0,<1.29.0)"] datasync = ["mypy-boto3-datasync (>=1.28.0,<1.29.0)"] +datazone = ["mypy-boto3-datazone (>=1.28.0,<1.29.0)"] dax = ["mypy-boto3-dax (>=1.28.0,<1.29.0)"] detective = ["mypy-boto3-detective (>=1.28.0,<1.29.0)"] devicefarm = ["mypy-boto3-devicefarm (>=1.28.0,<1.29.0)"] @@ -1710,6 +1712,20 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} +[[package]] +name = "mypy-boto3-sts" +version = "1.28.58" +description = "Type annotations for boto3.STS 1.28.58 service generated with mypy-boto3-builder 7.19.0" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-boto3-sts-1.28.58.tar.gz", hash = "sha256:beffec705f1f0b449da3c1f52ac7658627bb289aecec1a4408266479c46e053b"}, + {file = "mypy_boto3_sts-1.28.58-py3-none-any.whl", hash = "sha256:9bb44163aed4efa5d1f82084ea18c0cd5e6622dca62798c487e9604d9bb45c77"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -1918,6 +1934,17 @@ files = [ {file = "psycopg_binary-3.1.12-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:000838cb5ab7851116b462e58893a96b0f1e35864135a6283f3242a730ec45d3"}, {file = "psycopg_binary-3.1.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7949e1aefe339f04dbecac6aa036c9cd137a58f966c4b96ab933823c340ee12"}, {file = "psycopg_binary-3.1.12-cp311-cp311-win_amd64.whl", hash = "sha256:b32922872460575083487de41e17e8cf308c3550da02c704efe42960bc6c19de"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:70054ada2f890d004dc3d5ff908e34aecb085fd599d40db2975c09a39c50dfc3"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7544d6d74f5b5f9daafe8a4ed7d266787d62a2bf16f5120c45d42d1f4a856bc8"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43197161099cb4e36a9ca44c10657908b619d7263ffcff30932ad4627430dc3c"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68398cdf3aedd4042b1126b9aba34615f1ab592831483282f19f0159fce5ca75"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77ae6cda3ffee2425aca9ea7af57296d0c701e2ac5897b48b95dfee050234592"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278e8888e90fb6ebd7eae8ccb85199eafd712b734e641e0d40f2a903e946102d"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:047c4ba8d3089465b0a69c4c669128df43403867858d78da6b40b33788bfa89f"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8248b11ac490bb74de80457ab0e9cef31c08164ff7b867031927a17e5c9e19ed"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6979c02acb9783c6134ee516751b8f891a2d4db7f73ebecc9e92750283d6fb99"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:eaf2375b724ad61ee82a5c2a849e57b12b3cb510ec8845084132bbb907cb3335"}, + {file = "psycopg_binary-3.1.12-cp312-cp312-win_amd64.whl", hash = "sha256:6177cfa6f872a9cc84dbfc7dc163af6ef01639c50acc9a441673f29c2305c37a"}, {file = "psycopg_binary-3.1.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b81427fd5a97c9b4ac12f3b8d985870b0c3866b5fc2e72e51cacd3630ffd6466"}, {file = "psycopg_binary-3.1.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f17a2c393879aa54f840540009d0e70a30d22ffa0038d81e258ac2c99b15d74"}, {file = "psycopg_binary-3.1.12-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c6a5d125a61101ef5ab7384206e43952fe2a5fca997b96d28a28a752512f900"}, @@ -2509,7 +2536,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} typing-extensions = ">=4.2.0" [package.extras] @@ -2932,4 +2959,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "f61a2496bb6c22d96757b3bcbeea46b742d69518d987b760c99eca1d1e8e50d4" +content-hash = "8592123f1eedbdeb6ce03c8def6a8cef9483300b9fa8b42d051074dee77f821e" diff --git a/entities/pyproject.toml b/entities/pyproject.toml index b527ac68..a3c5f3b1 100644 --- a/entities/pyproject.toml +++ b/entities/pyproject.toml @@ -36,7 +36,7 @@ uuid6 = "^2023.5.2" mypy = "^1.5.1" types-requests = "^2.31.0.2" boto3 = "^1.28.43" -boto3-stubs = {extras = ["s3"], version = "^1.28.50"} +boto3-stubs = {extras = ["s3", "sts"], version = "^1.28.61"} faker-enum = "^0.0.2" biopython = "^1.81" moto = "^4.2.3" diff --git a/platformics/api/core/deps.py b/platformics/api/core/deps.py index 0f66d62a..2b8105da 100644 --- a/platformics/api/core/deps.py +++ b/platformics/api/core/deps.py @@ -6,6 +6,7 @@ from cerbos.sdk.model import Principal from fastapi import Depends from mypy_boto3_s3.client import S3Client +from mypy_boto3_sts.client import STSClient from platformics.api.core.settings import APISettings from platformics.database.connect import AsyncDB, init_async_db from platformics.security.token_auth import get_token_claims @@ -97,7 +98,7 @@ def get_s3_client( def get_sts_client( settings: APISettings = Depends(get_settings), -): +) -> STSClient: return boto3.client( "sts", region_name=settings.AWS_REGION, diff --git a/workflows/docker-compose.yml b/workflows/docker-compose.yml index 2ea8f38a..09a1d7dc 100644 --- a/workflows/docker-compose.yml +++ b/workflows/docker-compose.yml @@ -58,6 +58,7 @@ services: JWK_PUBLIC_KEY_FILE: "/workflows/test_infra/fixtures/public_key.pem" JWK_PRIVATE_KEY_FILE: "/workflows/test_infra/fixtures/private_key.pem" DEFAULT_UPLOAD_BUCKET: "local-bucket" + DEFAULT_UPLOAD_PROTOCOL: "S3" BOTO_ENDPOINT_URL: "http://motoserver.czidnet:4000" ENTITY_SERVICE_URL: "http://entity-service:8008" AWS_REGION: "us-west-2" From 10123a27ab8267ff49a3f922db0630f36fcc3b22 Mon Sep 17 00:00:00 2001 From: Robert Aboukhalil Date: Fri, 6 Oct 2023 09:42:39 -0700 Subject: [PATCH 3/5] Codegen --- entities/api/schema.graphql | 15 +++-- entities/api/schema.json | 118 +++++++++++++++++++++++++++++------- entities/cli/gql_schema.py | 27 +++++++-- 3 files changed, 129 insertions(+), 31 deletions(-) diff --git a/entities/api/schema.graphql b/entities/api/schema.graphql index 2a562a78..c385ec84 100644 --- a/entities/api/schema.graphql +++ b/entities/api/schema.graphql @@ -60,9 +60,6 @@ input FileUpload { name: String! fileFormat: String = null compressionType: String = null - protocol: String = null - namespace: String = null - path: String = null } """ @@ -70,13 +67,23 @@ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http:// """ scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") +type MultipartUploadCredentials { + protocol: String! + namespace: String! + path: String! + accessKeyId: String! + secretAccessKey: String! + sessionToken: String! + expiration: String! +} + type Mutation { createSample(name: String!, location: String!, collectionId: Int!): Sample! createSequencingRead(nucleotide: String!, sequence: String!, protocol: String!, sequenceFileId: UUID, sampleId: UUID!, collectionId: Int!): SequencingRead! createContig(sequence: String!, sequencingReadId: UUID!, collectionId: Int!): Contig! updateSample(entityId: UUID!, name: String!, location: String!): Sample! createFile(entityId: UUID!, entityFieldName: String!, file: FileCreate!): File! - uploadFile(entityId: UUID!, entityFieldName: String!, file: FileUpload!, expiration: Int! = 3600): SignedURL! + uploadFile(entityId: UUID!, entityFieldName: String!, file: FileUpload!, expiration: Int! = 3600): MultipartUploadCredentials! markUploadComplete(fileId: UUID!): File! } diff --git a/entities/api/schema.json b/entities/api/schema.json index 071f8f88..38451532 100644 --- a/entities/api/schema.json +++ b/entities/api/schema.json @@ -1521,7 +1521,7 @@ "name": null, "ofType": { "kind": "OBJECT", - "name": "SignedURL", + "name": "MultipartUploadCredentials", "ofType": null } } @@ -1642,11 +1642,10 @@ }, { "enumValues": null, - "fields": null, - "inputFields": [ + "fields": [ { - "defaultValue": null, - "name": "name", + "args": [], + "name": "protocol", "type": { "kind": "NON_NULL", "name": null, @@ -1658,35 +1657,110 @@ } }, { - "defaultValue": "null", - "name": "fileFormat", + "args": [], + "name": "namespace", "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } } }, { - "defaultValue": "null", - "name": "compressionType", + "args": [], + "name": "path", "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } } }, { - "defaultValue": "null", - "name": "protocol", + "args": [], + "name": "accessKeyId", "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "name": "secretAccessKey", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "name": "sessionToken", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + { + "args": [], + "name": "expiration", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + ], + "inputFields": null, + "interfaces": [], + "kind": "OBJECT", + "name": "MultipartUploadCredentials", + "possibleTypes": null + }, + { + "enumValues": null, + "fields": null, + "inputFields": [ + { + "defaultValue": null, + "name": "name", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } } }, { "defaultValue": "null", - "name": "namespace", + "name": "fileFormat", "type": { "kind": "SCALAR", "name": "String", @@ -1695,7 +1769,7 @@ }, { "defaultValue": "null", - "name": "path", + "name": "compressionType", "type": { "kind": "SCALAR", "name": "String", diff --git a/entities/cli/gql_schema.py b/entities/cli/gql_schema.py index cce00d4e..ec3fabc6 100644 --- a/entities/cli/gql_schema.py +++ b/entities/cli/gql_schema.py @@ -45,13 +45,10 @@ class FileCreate(sgqlc.types.Input): class FileUpload(sgqlc.types.Input): __schema__ = gql_schema - __field_names__ = ("name", "file_format", "compression_type", "protocol", "namespace", "path") + __field_names__ = ("name", "file_format", "compression_type") name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="name") file_format = sgqlc.types.Field(String, graphql_name="fileFormat") compression_type = sgqlc.types.Field(String, graphql_name="compressionType") - protocol = sgqlc.types.Field(String, graphql_name="protocol") - namespace = sgqlc.types.Field(String, graphql_name="namespace") - path = sgqlc.types.Field(String, graphql_name="path") ######################################################################## @@ -117,6 +114,26 @@ class File(sgqlc.types.Type): ) +class MultipartUploadCredentials(sgqlc.types.Type): + __schema__ = gql_schema + __field_names__ = ( + "protocol", + "namespace", + "path", + "access_key_id", + "secret_access_key", + "session_token", + "expiration", + ) + protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="protocol") + namespace = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="namespace") + path = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="path") + access_key_id = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="accessKeyId") + secret_access_key = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="secretAccessKey") + session_token = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="sessionToken") + expiration = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="expiration") + + class Mutation(sgqlc.types.Type): __schema__ = gql_schema __field_names__ = ( @@ -202,7 +219,7 @@ class Mutation(sgqlc.types.Type): ), ) upload_file = sgqlc.types.Field( - sgqlc.types.non_null("SignedURL"), + sgqlc.types.non_null(MultipartUploadCredentials), graphql_name="uploadFile", args=sgqlc.types.ArgDict( ( From e5fb545032a5e2869dbbea9d27d66a0187abeb11 Mon Sep 17 00:00:00 2001 From: Robert Aboukhalil Date: Mon, 9 Oct 2023 10:26:50 -0700 Subject: [PATCH 4/5] Cleanup --- workflows/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/workflows/docker-compose.yml b/workflows/docker-compose.yml index 66a97fae..8643764b 100644 --- a/workflows/docker-compose.yml +++ b/workflows/docker-compose.yml @@ -58,7 +58,6 @@ services: JWK_PUBLIC_KEY_FILE: "/workflows/test_infra/fixtures/public_key.pem" JWK_PRIVATE_KEY_FILE: "/workflows/test_infra/fixtures/private_key.pem" DEFAULT_UPLOAD_BUCKET: "local-bucket" - DEFAULT_UPLOAD_PROTOCOL: "S3" BOTO_ENDPOINT_URL: "http://motoserver.czidnet:4000" ENTITY_SERVICE_URL: "http://entity-service:8008" ENTITY_SERVICE_AUTH_TOKEN: "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImVwayI6eyJjcnYiOiJQLTM4NCIsImt0eSI6IkVDIiwieCI6Ik5Nc3ZKbXVuYnBXY0VsdVlJTmRVeVVIcUkzbjZCR2VQd2V3ajRXS0pVdEt0QXhmUUtrVE81M2kzQ2dSZkZYVEQiLCJ5IjoiYmp6TkJuZjExWjRIV3dBVm95UVpSOGRWSERicW9wTjhvQkJZYnIxQlBiU1llZHdaWkVuYzJqS21rY0xxcloxTiJ9LCJraWQiOiItQmx2bF9wVk5LU2JRQ2N5dGV4UzNfMk5MaHBia2J6LVk5VFFjbkY5S1drIiwidHlwIjoiSldFIn0..Ymjmtj6nXp8r8AFe8AgI1g.e_39w7OXGJaOVKL_QkP38rvlcEeSgFQsxT0rTdCgI5E-b328zlVHagLSFZ_Iqvqiy6Z8KcU4pLJ3NTaW3Ys_YQsnUn6yUEvwqOY2KESB0mT0Bp3qpNYRBZJVA8PW43YAbOnO7h7ZTwQZJfwMzwhcaaYeZW8pN9rvcNtQX3rgBubSuR-LHKL6k4uAMPh9A8ZxXKZgpI6tpJPxE-uspvYi-foW8VyjZtwOUMvMp3lfZPyL1oQIv_rEUhOGNO_lfi339QcT6F7DwBjXK6C_7U65F-dFZScnReLnVczPfHhJ7z3NnVt46sFcddgZpLIpQyzA6puCcDoRm5ZZCVvm8h-LHVy-9dGWLVxBRhGRdBwBhbiVu2O_CNeVabXl8JhAs3oeV2zDgYfOj_-kkHWsbgHZ0y-tc-HtgoXzsUqaRP1IXQ3g3VDES7UjsaKsfxgURH5EIsrdWwFrWHGoLNfGwwPSwTBI5Mul7LT10-Pg_uBBCiHfQIDqerRQeADRFhV_07GYatBDt-RxwNL4bO59V8ewCzhpdCYRpL363HGldT1Pic-SpTk2NsY2t8MA6__FhJU9JSKYwJpeKMaGLUHA_40PEQ.gb5q-WZTU-ZKpV7WYFbMGMEF2CZIBrFlCUeaZ5ffPDU" From 85f44255b2ce27e7c9ddda7c62bacc76574e2359 Mon Sep 17 00:00:00 2001 From: Robert Aboukhalil Date: Mon, 9 Oct 2023 10:42:50 -0700 Subject: [PATCH 5/5] Update tests --- entities/api/files.py | 5 +- entities/api/tests/test_file_writes.py | 25 +- workflows/entity_gql_schema.py | 364 ++++++++++--------------- 3 files changed, 160 insertions(+), 234 deletions(-) diff --git a/entities/api/files.py b/entities/api/files.py index 4c433229..e6297510 100644 --- a/entities/api/files.py +++ b/entities/api/files.py @@ -114,7 +114,7 @@ def generate_multipart_upload_token( new_file: db.File, expiration: int = 3600, sts_client: STSClient = Depends(get_sts_client), -): +) -> MultipartUploadCredentials: policy = { "Version": "2012-10-17", "Statement": [ @@ -182,10 +182,11 @@ async def create_file( cerbos_client: CerbosClient = Depends(get_cerbos_client), principal: Principal = Depends(require_auth_principal), s3_client: S3Client = Depends(get_s3_client), + sts_client: STSClient = Depends(get_sts_client), settings: APISettings = Depends(get_settings), ) -> db.File: new_file = await create_or_upload_file( - entity_id, entity_field_name, file, -1, session, cerbos_client, principal, s3_client, None, settings + entity_id, entity_field_name, file, -1, session, cerbos_client, principal, s3_client, sts_client, settings ) assert isinstance(new_file, db.File) # reassure mypy that we're returning the right type return new_file diff --git a/entities/api/tests/test_file_writes.py b/entities/api/tests/test_file_writes.py index dc6d9980..d7682131 100644 --- a/entities/api/tests/test_file_writes.py +++ b/entities/api/tests/test_file_writes.py @@ -89,7 +89,7 @@ async def test_invalid_fastq( assert fileinfo["status"] == "FAILED" -# Test generating signed URLs for file upload +# Test generating STS tokens for file uploads @pytest.mark.asyncio @pytest.mark.parametrize( "member_projects,project_id,entity_field", @@ -121,14 +121,19 @@ async def test_upload_file( # Try creating a file mutation = f""" mutation MyQuery {{ - uploadFile(entityId: "{entity_id}", entityFieldName: "{entity_field}", file: {{ - name: "test.fastq", fileFormat: "fastq" - }}) {{ - url + uploadFile( + entityId: "{entity_id}", + entityFieldName: "{entity_field}", + file: {{ + name: "test.fastq", + fileFormat: "fastq" + }} + ) {{ + namespace + path + accessKeyId + secretAccessKey expiration - method - protocol - fields }} }} """ @@ -140,7 +145,9 @@ async def test_upload_file( assert output["errors"] is not None return - assert output["data"]["uploadFile"]["url"] == "https://local-bucket.s3.amazonaws.com/" + # Moto produces a hard-coded tokens + assert output["data"]["uploadFile"]["accessKeyId"].endswith("EXAMPLE") + assert output["data"]["uploadFile"]["secretAccessKey"].endswith("EXAMPLEKEY") # Test adding an existing file to the entities service diff --git a/workflows/entity_gql_schema.py b/workflows/entity_gql_schema.py index cce00d4e..dadcd12b 100644 --- a/workflows/entity_gql_schema.py +++ b/workflows/entity_gql_schema.py @@ -4,54 +4,51 @@ gql_schema = sgqlc.types.Schema() + ######################################################################## # Scalars and Enumerations ######################################################################## Boolean = sgqlc.types.Boolean - class FileStatus(sgqlc.types.Enum): __schema__ = gql_schema - __choices__ = ("FAILED", "PENDING", "SUCCESS") + __choices__ = ('FAILED', 'PENDING', 'SUCCESS') Int = sgqlc.types.Int - class JSON(sgqlc.types.Scalar): __schema__ = gql_schema String = sgqlc.types.String - class UUID(sgqlc.types.Scalar): __schema__ = gql_schema + ######################################################################## # Input Objects ######################################################################## class FileCreate(sgqlc.types.Input): __schema__ = gql_schema - __field_names__ = ("name", "file_format", "compression_type", "protocol", "namespace", "path") - name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="name") - file_format = sgqlc.types.Field(String, graphql_name="fileFormat") - compression_type = sgqlc.types.Field(String, graphql_name="compressionType") - protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="protocol") - namespace = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="namespace") - path = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="path") + __field_names__ = ('name', 'file_format', 'compression_type', 'protocol', 'namespace', 'path') + name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='name') + file_format = sgqlc.types.Field(String, graphql_name='fileFormat') + compression_type = sgqlc.types.Field(String, graphql_name='compressionType') + protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='protocol') + namespace = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='namespace') + path = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='path') class FileUpload(sgqlc.types.Input): __schema__ = gql_schema - __field_names__ = ("name", "file_format", "compression_type", "protocol", "namespace", "path") - name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="name") - file_format = sgqlc.types.Field(String, graphql_name="fileFormat") - compression_type = sgqlc.types.Field(String, graphql_name="compressionType") - protocol = sgqlc.types.Field(String, graphql_name="protocol") - namespace = sgqlc.types.Field(String, graphql_name="namespace") - path = sgqlc.types.Field(String, graphql_name="path") + __field_names__ = ('name', 'file_format', 'compression_type') + name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='name') + file_format = sgqlc.types.Field(String, graphql_name='fileFormat') + compression_type = sgqlc.types.Field(String, graphql_name='compressionType') + ######################################################################## @@ -59,261 +56,181 @@ class FileUpload(sgqlc.types.Input): ######################################################################## class EntityInterface(sgqlc.types.Interface): __schema__ = gql_schema - __field_names__ = ("id", "type", "producing_run_id", "owner_user_id", "collection_id") - id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="id") - type = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="type") - producing_run_id = sgqlc.types.Field(Int, graphql_name="producingRunId") - owner_user_id = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name="ownerUserId") - collection_id = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name="collectionId") + __field_names__ = ('id', 'type', 'producing_run_id', 'owner_user_id', 'collection_id') + id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='id') + type = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='type') + producing_run_id = sgqlc.types.Field(Int, graphql_name='producingRunId') + owner_user_id = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name='ownerUserId') + collection_id = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name='collectionId') class ContigConnection(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ("edges",) - edges = sgqlc.types.Field( - sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null("ContigEdge"))), graphql_name="edges" - ) + __field_names__ = ('edges',) + edges = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null('ContigEdge'))), graphql_name='edges') class ContigEdge(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ("node",) - node = sgqlc.types.Field(sgqlc.types.non_null("Contig"), graphql_name="node") + __field_names__ = ('node',) + node = sgqlc.types.Field(sgqlc.types.non_null('Contig'), graphql_name='node') class File(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ( - "id", - "entity_id", - "entity_field_name", - "status", - "protocol", - "namespace", - "path", - "file_format", - "compression_type", - "size", - "entity", - "download_link", - ) - id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="id") - entity_id = sgqlc.types.Field(UUID, graphql_name="entityId") - entity_field_name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="entityFieldName") - status = sgqlc.types.Field(sgqlc.types.non_null(FileStatus), graphql_name="status") - protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="protocol") - namespace = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="namespace") - path = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="path") - file_format = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="fileFormat") - compression_type = sgqlc.types.Field(String, graphql_name="compressionType") - size = sgqlc.types.Field(Int, graphql_name="size") - entity = sgqlc.types.Field(EntityInterface, graphql_name="entity") - download_link = sgqlc.types.Field( - "SignedURL", - graphql_name="downloadLink", - args=sgqlc.types.ArgDict( - (("expiration", sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name="expiration", default=3600)),) - ), + __field_names__ = ('id', 'entity_id', 'entity_field_name', 'status', 'protocol', 'namespace', 'path', 'file_format', 'compression_type', 'size', 'entity', 'download_link') + id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='id') + entity_id = sgqlc.types.Field(UUID, graphql_name='entityId') + entity_field_name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='entityFieldName') + status = sgqlc.types.Field(sgqlc.types.non_null(FileStatus), graphql_name='status') + protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='protocol') + namespace = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='namespace') + path = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='path') + file_format = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='fileFormat') + compression_type = sgqlc.types.Field(String, graphql_name='compressionType') + size = sgqlc.types.Field(Int, graphql_name='size') + entity = sgqlc.types.Field(EntityInterface, graphql_name='entity') + download_link = sgqlc.types.Field('SignedURL', graphql_name='downloadLink', args=sgqlc.types.ArgDict(( + ('expiration', sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name='expiration', default=3600)), +)) ) +class MultipartUploadCredentials(sgqlc.types.Type): + __schema__ = gql_schema + __field_names__ = ('protocol', 'namespace', 'path', 'access_key_id', 'secret_access_key', 'session_token', 'expiration') + protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='protocol') + namespace = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='namespace') + path = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='path') + access_key_id = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='accessKeyId') + secret_access_key = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='secretAccessKey') + session_token = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='sessionToken') + expiration = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='expiration') + + class Mutation(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ( - "create_sample", - "create_sequencing_read", - "create_contig", - "update_sample", - "create_file", - "upload_file", - "mark_upload_complete", - ) - create_sample = sgqlc.types.Field( - sgqlc.types.non_null("Sample"), - graphql_name="createSample", - args=sgqlc.types.ArgDict( - ( - ("name", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="name", default=None)), - ("location", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="location", default=None)), - ( - "collection_id", - sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name="collectionId", default=None), - ), - ) - ), - ) - create_sequencing_read = sgqlc.types.Field( - sgqlc.types.non_null("SequencingRead"), - graphql_name="createSequencingRead", - args=sgqlc.types.ArgDict( - ( - ("nucleotide", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="nucleotide", default=None)), - ("sequence", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="sequence", default=None)), - ("protocol", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="protocol", default=None)), - ("sequence_file_id", sgqlc.types.Arg(UUID, graphql_name="sequenceFileId", default=None)), - ("sample_id", sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name="sampleId", default=None)), - ( - "collection_id", - sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name="collectionId", default=None), - ), - ) - ), - ) - create_contig = sgqlc.types.Field( - sgqlc.types.non_null("Contig"), - graphql_name="createContig", - args=sgqlc.types.ArgDict( - ( - ("sequence", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="sequence", default=None)), - ( - "sequencing_read_id", - sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name="sequencingReadId", default=None), - ), - ( - "collection_id", - sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name="collectionId", default=None), - ), - ) - ), - ) - update_sample = sgqlc.types.Field( - sgqlc.types.non_null("Sample"), - graphql_name="updateSample", - args=sgqlc.types.ArgDict( - ( - ("entity_id", sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name="entityId", default=None)), - ("name", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="name", default=None)), - ("location", sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="location", default=None)), - ) - ), - ) - create_file = sgqlc.types.Field( - sgqlc.types.non_null(File), - graphql_name="createFile", - args=sgqlc.types.ArgDict( - ( - ("entity_id", sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name="entityId", default=None)), - ( - "entity_field_name", - sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="entityFieldName", default=None), - ), - ("file", sgqlc.types.Arg(sgqlc.types.non_null(FileCreate), graphql_name="file", default=None)), - ) - ), - ) - upload_file = sgqlc.types.Field( - sgqlc.types.non_null("SignedURL"), - graphql_name="uploadFile", - args=sgqlc.types.ArgDict( - ( - ("entity_id", sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name="entityId", default=None)), - ( - "entity_field_name", - sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name="entityFieldName", default=None), - ), - ("file", sgqlc.types.Arg(sgqlc.types.non_null(FileUpload), graphql_name="file", default=None)), - ("expiration", sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name="expiration", default=3600)), - ) - ), - ) - mark_upload_complete = sgqlc.types.Field( - sgqlc.types.non_null(File), - graphql_name="markUploadComplete", - args=sgqlc.types.ArgDict( - (("file_id", sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name="fileId", default=None)),) - ), + __field_names__ = ('create_sample', 'create_sequencing_read', 'create_contig', 'update_sample', 'create_file', 'upload_file', 'mark_upload_complete') + create_sample = sgqlc.types.Field(sgqlc.types.non_null('Sample'), graphql_name='createSample', args=sgqlc.types.ArgDict(( + ('name', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='name', default=None)), + ('location', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='location', default=None)), + ('collection_id', sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name='collectionId', default=None)), +)) + ) + create_sequencing_read = sgqlc.types.Field(sgqlc.types.non_null('SequencingRead'), graphql_name='createSequencingRead', args=sgqlc.types.ArgDict(( + ('nucleotide', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='nucleotide', default=None)), + ('sequence', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='sequence', default=None)), + ('protocol', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='protocol', default=None)), + ('sequence_file_id', sgqlc.types.Arg(UUID, graphql_name='sequenceFileId', default=None)), + ('sample_id', sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name='sampleId', default=None)), + ('collection_id', sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name='collectionId', default=None)), +)) + ) + create_contig = sgqlc.types.Field(sgqlc.types.non_null('Contig'), graphql_name='createContig', args=sgqlc.types.ArgDict(( + ('sequence', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='sequence', default=None)), + ('sequencing_read_id', sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name='sequencingReadId', default=None)), + ('collection_id', sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name='collectionId', default=None)), +)) + ) + update_sample = sgqlc.types.Field(sgqlc.types.non_null('Sample'), graphql_name='updateSample', args=sgqlc.types.ArgDict(( + ('entity_id', sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name='entityId', default=None)), + ('name', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='name', default=None)), + ('location', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='location', default=None)), +)) + ) + create_file = sgqlc.types.Field(sgqlc.types.non_null(File), graphql_name='createFile', args=sgqlc.types.ArgDict(( + ('entity_id', sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name='entityId', default=None)), + ('entity_field_name', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='entityFieldName', default=None)), + ('file', sgqlc.types.Arg(sgqlc.types.non_null(FileCreate), graphql_name='file', default=None)), +)) + ) + upload_file = sgqlc.types.Field(sgqlc.types.non_null(MultipartUploadCredentials), graphql_name='uploadFile', args=sgqlc.types.ArgDict(( + ('entity_id', sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name='entityId', default=None)), + ('entity_field_name', sgqlc.types.Arg(sgqlc.types.non_null(String), graphql_name='entityFieldName', default=None)), + ('file', sgqlc.types.Arg(sgqlc.types.non_null(FileUpload), graphql_name='file', default=None)), + ('expiration', sgqlc.types.Arg(sgqlc.types.non_null(Int), graphql_name='expiration', default=3600)), +)) + ) + mark_upload_complete = sgqlc.types.Field(sgqlc.types.non_null(File), graphql_name='markUploadComplete', args=sgqlc.types.ArgDict(( + ('file_id', sgqlc.types.Arg(sgqlc.types.non_null(UUID), graphql_name='fileId', default=None)), +)) ) class Query(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ("samples", "sequencing_reads", "contigs", "files") - samples = sgqlc.types.Field( - sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null("Sample"))), - graphql_name="samples", - args=sgqlc.types.ArgDict((("id", sgqlc.types.Arg(UUID, graphql_name="id", default=None)),)), + __field_names__ = ('samples', 'sequencing_reads', 'contigs', 'files') + samples = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null('Sample'))), graphql_name='samples', args=sgqlc.types.ArgDict(( + ('id', sgqlc.types.Arg(UUID, graphql_name='id', default=None)), +)) ) - sequencing_reads = sgqlc.types.Field( - sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null("SequencingRead"))), - graphql_name="sequencingReads", - args=sgqlc.types.ArgDict((("id", sgqlc.types.Arg(UUID, graphql_name="id", default=None)),)), + sequencing_reads = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null('SequencingRead'))), graphql_name='sequencingReads', args=sgqlc.types.ArgDict(( + ('id', sgqlc.types.Arg(UUID, graphql_name='id', default=None)), +)) ) - contigs = sgqlc.types.Field( - sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null("Contig"))), - graphql_name="contigs", - args=sgqlc.types.ArgDict((("id", sgqlc.types.Arg(UUID, graphql_name="id", default=None)),)), + contigs = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null('Contig'))), graphql_name='contigs', args=sgqlc.types.ArgDict(( + ('id', sgqlc.types.Arg(UUID, graphql_name='id', default=None)), +)) ) - files = sgqlc.types.Field( - sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null(File))), - graphql_name="files", - args=sgqlc.types.ArgDict((("id", sgqlc.types.Arg(UUID, graphql_name="id", default=None)),)), + files = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null(File))), graphql_name='files', args=sgqlc.types.ArgDict(( + ('id', sgqlc.types.Arg(UUID, graphql_name='id', default=None)), +)) ) class SequencingReadConnection(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ("edges",) - edges = sgqlc.types.Field( - sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null("SequencingReadEdge"))), graphql_name="edges" - ) + __field_names__ = ('edges',) + edges = sgqlc.types.Field(sgqlc.types.non_null(sgqlc.types.list_of(sgqlc.types.non_null('SequencingReadEdge'))), graphql_name='edges') class SequencingReadEdge(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ("node",) - node = sgqlc.types.Field(sgqlc.types.non_null("SequencingRead"), graphql_name="node") + __field_names__ = ('node',) + node = sgqlc.types.Field(sgqlc.types.non_null('SequencingRead'), graphql_name='node') class SignedURL(sgqlc.types.Type): __schema__ = gql_schema - __field_names__ = ("url", "protocol", "method", "expiration", "fields") - url = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="url") - protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="protocol") - method = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="method") - expiration = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name="expiration") - fields = sgqlc.types.Field(JSON, graphql_name="fields") + __field_names__ = ('url', 'protocol', 'method', 'expiration', 'fields') + url = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='url') + protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='protocol') + method = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='method') + expiration = sgqlc.types.Field(sgqlc.types.non_null(Int), graphql_name='expiration') + fields = sgqlc.types.Field(JSON, graphql_name='fields') class Contig(sgqlc.types.Type, EntityInterface): __schema__ = gql_schema - __field_names__ = ("entity_id", "sequence", "sequencing_read_id", "sequencing_read") - entity_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="entityId") - sequence = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="sequence") - sequencing_read_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="sequencingReadId") - sequencing_read = sgqlc.types.Field(sgqlc.types.non_null("SequencingRead"), graphql_name="sequencingRead") + __field_names__ = ('entity_id', 'sequence', 'sequencing_read_id', 'sequencing_read') + entity_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='entityId') + sequence = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='sequence') + sequencing_read_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='sequencingReadId') + sequencing_read = sgqlc.types.Field(sgqlc.types.non_null('SequencingRead'), graphql_name='sequencingRead') class Sample(sgqlc.types.Type, EntityInterface): __schema__ = gql_schema - __field_names__ = ("entity_id", "name", "location", "sequencing_reads") - entity_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="entityId") - name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="name") - location = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="location") - sequencing_reads = sgqlc.types.Field(sgqlc.types.non_null(SequencingReadConnection), graphql_name="sequencingReads") + __field_names__ = ('entity_id', 'name', 'location', 'sequencing_reads') + entity_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='entityId') + name = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='name') + location = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='location') + sequencing_reads = sgqlc.types.Field(sgqlc.types.non_null(SequencingReadConnection), graphql_name='sequencingReads') class SequencingRead(sgqlc.types.Type, EntityInterface): __schema__ = gql_schema - __field_names__ = ( - "entity_id", - "nucleotide", - "sequence", - "protocol", - "sequence_file_id", - "sample_id", - "sequence_file", - "sample", - "contigs", - ) - entity_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="entityId") - nucleotide = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="nucleotide") - sequence = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="sequence") - protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name="protocol") - sequence_file_id = sgqlc.types.Field(UUID, graphql_name="sequenceFileId") - sample_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name="sampleId") - sequence_file = sgqlc.types.Field(File, graphql_name="sequenceFile") - sample = sgqlc.types.Field(sgqlc.types.non_null(Sample), graphql_name="sample") - contigs = sgqlc.types.Field(sgqlc.types.non_null(ContigConnection), graphql_name="contigs") + __field_names__ = ('entity_id', 'nucleotide', 'sequence', 'protocol', 'sequence_file_id', 'sample_id', 'sequence_file', 'sample', 'contigs') + entity_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='entityId') + nucleotide = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='nucleotide') + sequence = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='sequence') + protocol = sgqlc.types.Field(sgqlc.types.non_null(String), graphql_name='protocol') + sequence_file_id = sgqlc.types.Field(UUID, graphql_name='sequenceFileId') + sample_id = sgqlc.types.Field(sgqlc.types.non_null(UUID), graphql_name='sampleId') + sequence_file = sgqlc.types.Field(File, graphql_name='sequenceFile') + sample = sgqlc.types.Field(sgqlc.types.non_null(Sample), graphql_name='sample') + contigs = sgqlc.types.Field(sgqlc.types.non_null(ContigConnection), graphql_name='contigs') + ######################################################################## @@ -326,3 +243,4 @@ class SequencingRead(sgqlc.types.Type, EntityInterface): gql_schema.query_type = Query gql_schema.mutation_type = Mutation gql_schema.subscription_type = None +