From 64fa769a1294e59c8dcea115ccd1eb38b7143cf4 Mon Sep 17 00:00:00 2001 From: Noah Meyerhans Date: Wed, 25 May 2022 16:13:54 -0700 Subject: [PATCH] New upstream version 1.23.8+dfsg --- .changes/1.20.35.json | 32 + .changes/1.20.36.json | 27 + .changes/1.20.37.json | 7 + .changes/1.20.38.json | 27 + .changes/1.20.39.json | 12 + .changes/1.20.40.json | 27 + .changes/1.20.41.json | 17 + .changes/1.20.42.json | 7 + .changes/1.20.43.json | 22 + .changes/1.20.44.json | 27 + .changes/1.20.45.json | 27 + .changes/1.20.46.json | 32 + .changes/1.20.47.json | 62 ++ .changes/1.20.48.json | 22 + .changes/1.20.49.json | 17 + .changes/1.20.50.json | 27 + .changes/1.20.51.json | 22 + .changes/1.20.52.json | 7 + .changes/1.20.53.json | 27 + .changes/1.20.54.json | 7 + .changes/1.21.0.json | 22 + .changes/1.21.1.json | 27 + .changes/1.21.10.json | 22 + .changes/1.21.11.json | 27 + .changes/1.21.12.json | 27 + .changes/1.21.13.json | 37 + .changes/1.21.14.json | 17 + .changes/1.21.15.json | 17 + .changes/1.21.16.json | 7 + .changes/1.21.17.json | 17 + .changes/1.21.18.json | 27 + .changes/1.21.19.json | 27 + .changes/1.21.2.json | 22 + .changes/1.21.20.json | 47 + .changes/1.21.21.json | 37 + .changes/1.21.22.json | 12 + .changes/1.21.23.json | 27 + .changes/1.21.24.json | 27 + .changes/1.21.25.json | 37 + .changes/1.21.26.json | 17 + .changes/1.21.27.json | 17 + .changes/1.21.28.json | 12 + .changes/1.21.29.json | 7 + .changes/1.21.3.json | 17 + .changes/1.21.30.json | 27 + .changes/1.21.31.json | 37 + .changes/1.21.32.json | 7 + .changes/1.21.33.json | 27 + .changes/1.21.34.json | 22 + .changes/1.21.35.json | 27 + .changes/1.21.36.json | 32 + .changes/1.21.37.json | 17 + .changes/1.21.38.json | 17 + .changes/1.21.39.json | 22 + .changes/1.21.4.json | 17 + .changes/1.21.40.json | 12 + .changes/1.21.41.json | 32 + .changes/1.21.42.json | 17 + .changes/1.21.43.json | 52 ++ .changes/1.21.44.json | 22 + .changes/1.21.45.json | 47 + .changes/1.21.46.json | 12 + .changes/1.21.5.json | 17 + .changes/1.21.6.json | 17 + .changes/1.21.7.json | 42 + .changes/1.21.8.json | 12 + .changes/1.21.9.json | 47 + .changes/1.22.0.json | 32 + .changes/1.22.1.json | 37 + .changes/1.22.10.json | 17 + .changes/1.22.11.json | 27 + .changes/1.22.12.json | 12 + .changes/1.22.13.json | 57 ++ .changes/1.22.2.json | 37 + .changes/1.22.3.json | 32 + .changes/1.22.4.json | 27 + .changes/1.22.5.json | 17 + .changes/1.22.6.json | 27 + .changes/1.22.7.json | 22 + .changes/1.22.8.json | 22 + .changes/1.22.9.json | 32 + .changes/1.23.0.json | 12 + .changes/1.23.1.json | 37 + .changes/1.23.2.json | 12 + .changes/1.23.3.json | 32 + .changes/1.23.4.json | 17 + .changes/1.23.5.json | 12 + .changes/1.23.6.json | 17 + .changes/1.23.7.json | 27 + .changes/1.23.8.json | 42 + .github/ISSUE_TEMPLATE/bug-report.yml | 73 ++ .github/ISSUE_TEMPLATE/bug_report.md | 20 - .github/ISSUE_TEMPLATE/config.yml | 7 +- .github/ISSUE_TEMPLATE/documentation.yml | 23 + .github/ISSUE_TEMPLATE/feature-request.yml | 59 ++ .github/ISSUE_TEMPLATE/feature_request.md | 14 - .github/ISSUE_TEMPLATE/guidance-issue.md | 18 - .pre-commit-config.yaml | 30 +- CHANGELOG.rst | 853 ++++++++++++++++++ README.rst | 49 +- boto3/__init__.py | 5 +- boto3/compat.py | 20 +- boto3/docs/__init__.py | 3 +- boto3/docs/action.py | 79 +- boto3/docs/attr.py | 34 +- boto3/docs/base.py | 7 +- boto3/docs/client.py | 3 +- boto3/docs/collection.py | 172 ++-- boto3/docs/docstring.py | 19 +- boto3/docs/method.py | 45 +- boto3/docs/resource.py | 125 +-- boto3/docs/service.py | 53 +- boto3/docs/subresource.py | 59 +- boto3/docs/utils.py | 69 +- boto3/docs/waiter.py | 62 +- boto3/dynamodb/conditions.py | 105 ++- boto3/dynamodb/table.py | 50 +- boto3/dynamodb/transform.py | 129 ++- boto3/dynamodb/types.py | 78 +- boto3/ec2/deletetags.py | 15 +- boto3/examples/s3.rst | 39 + boto3/exceptions.py | 52 +- boto3/resources/action.py | 59 +- boto3/resources/base.py | 33 +- boto3/resources/collection.py | 168 ++-- boto3/resources/factory.py | 255 ++++-- boto3/resources/model.py | 86 +- boto3/resources/params.py | 9 +- boto3/resources/response.py | 58 +- boto3/s3/inject.py | 306 +++++-- boto3/s3/transfer.py | 46 +- boto3/session.py | 173 ++-- boto3/utils.py | 21 +- docs/source/guide/configuration.rst | 2 +- .../guide/ec2-example-security-group.rst | 12 +- docs/source/guide/quickstart.rst | 12 +- .../guide/s3-example-configuring-buckets.rst | 2 +- docs/source/guide/security.rst | 44 +- pyproject.toml | 14 + requirements-dev-lock.txt | 15 +- requirements-dev.txt | 1 - requirements.txt | 3 +- scripts/ci/run-crt-tests | 2 +- scripts/ci/run-tests | 4 +- scripts/new-change | 75 +- setup.cfg | 4 +- setup.py | 13 +- tests/__init__.py | 5 +- tests/functional/docs/__init__.py | 14 +- tests/functional/docs/test_dynamodb.py | 194 ++-- tests/functional/docs/test_ec2.py | 7 +- tests/functional/docs/test_s3.py | 24 +- tests/functional/docs/test_smoke.py | 80 +- .../dynamodb/test_stubber_conditions.py | 19 +- tests/functional/dynamodb/test_table.py | 7 +- tests/functional/test_collection.py | 18 +- tests/functional/test_dynamodb.py | 20 +- tests/functional/test_ec2.py | 16 +- tests/functional/test_resource.py | 9 +- tests/functional/test_s3.py | 248 ++--- tests/functional/test_session.py | 3 +- tests/functional/test_smoke.py | 6 +- tests/functional/test_utils.py | 13 +- tests/integration/test_collections.py | 13 +- tests/integration/test_dynamodb.py | 110 ++- tests/integration/test_s3.py | 292 +++--- tests/integration/test_session.py | 10 +- tests/integration/test_sqs.py | 3 +- tests/unit/docs/__init__.py | 228 +++-- tests/unit/docs/test_action.py | 135 +-- tests/unit/docs/test_attr.py | 98 +- tests/unit/docs/test_client.py | 91 +- tests/unit/docs/test_collection.py | 185 ++-- tests/unit/docs/test_docstring.py | 349 ++++--- tests/unit/docs/test_method.py | 431 +++++---- tests/unit/docs/test_resource.py | 146 +-- tests/unit/docs/test_service.py | 15 +- tests/unit/docs/test_subresource.py | 23 +- tests/unit/docs/test_utils.py | 5 +- tests/unit/docs/test_waiter.py | 45 +- tests/unit/dynamodb/test_conditions.py | 360 +++++--- tests/unit/dynamodb/test_table.py | 165 ++-- tests/unit/dynamodb/test_transform.py | 355 ++++---- tests/unit/dynamodb/test_types.py | 46 +- tests/unit/ec2/test_createtags.py | 19 +- tests/unit/ec2/test_deletetags.py | 5 +- tests/unit/resources/test_action.py | 134 +-- tests/unit/resources/test_collection.py | 342 +++---- tests/unit/resources/test_collection_smoke.py | 32 +- tests/unit/resources/test_factory.py | 425 ++++----- tests/unit/resources/test_model.py | 447 ++++----- tests/unit/resources/test_params.py | 227 +++-- tests/unit/resources/test_response.py | 174 ++-- tests/unit/s3/test_inject.py | 160 +++- tests/unit/s3/test_transfer.py | 76 +- tests/unit/test_boto3.py | 35 +- tests/unit/test_session.py | 116 ++- tests/unit/test_utils.py | 13 +- 198 files changed, 8162 insertions(+), 3812 deletions(-) create mode 100644 .changes/1.20.35.json create mode 100644 .changes/1.20.36.json create mode 100644 .changes/1.20.37.json create mode 100644 .changes/1.20.38.json create mode 100644 .changes/1.20.39.json create mode 100644 .changes/1.20.40.json create mode 100644 .changes/1.20.41.json create mode 100644 .changes/1.20.42.json create mode 100644 .changes/1.20.43.json create mode 100644 .changes/1.20.44.json create mode 100644 .changes/1.20.45.json create mode 100644 .changes/1.20.46.json create mode 100644 .changes/1.20.47.json create mode 100644 .changes/1.20.48.json create mode 100644 .changes/1.20.49.json create mode 100644 .changes/1.20.50.json create mode 100644 .changes/1.20.51.json create mode 100644 .changes/1.20.52.json create mode 100644 .changes/1.20.53.json create mode 100644 .changes/1.20.54.json create mode 100644 .changes/1.21.0.json create mode 100644 .changes/1.21.1.json create mode 100644 .changes/1.21.10.json create mode 100644 .changes/1.21.11.json create mode 100644 .changes/1.21.12.json create mode 100644 .changes/1.21.13.json create mode 100644 .changes/1.21.14.json create mode 100644 .changes/1.21.15.json create mode 100644 .changes/1.21.16.json create mode 100644 .changes/1.21.17.json create mode 100644 .changes/1.21.18.json create mode 100644 .changes/1.21.19.json create mode 100644 .changes/1.21.2.json create mode 100644 .changes/1.21.20.json create mode 100644 .changes/1.21.21.json create mode 100644 .changes/1.21.22.json create mode 100644 .changes/1.21.23.json create mode 100644 .changes/1.21.24.json create mode 100644 .changes/1.21.25.json create mode 100644 .changes/1.21.26.json create mode 100644 .changes/1.21.27.json create mode 100644 .changes/1.21.28.json create mode 100644 .changes/1.21.29.json create mode 100644 .changes/1.21.3.json create mode 100644 .changes/1.21.30.json create mode 100644 .changes/1.21.31.json create mode 100644 .changes/1.21.32.json create mode 100644 .changes/1.21.33.json create mode 100644 .changes/1.21.34.json create mode 100644 .changes/1.21.35.json create mode 100644 .changes/1.21.36.json create mode 100644 .changes/1.21.37.json create mode 100644 .changes/1.21.38.json create mode 100644 .changes/1.21.39.json create mode 100644 .changes/1.21.4.json create mode 100644 .changes/1.21.40.json create mode 100644 .changes/1.21.41.json create mode 100644 .changes/1.21.42.json create mode 100644 .changes/1.21.43.json create mode 100644 .changes/1.21.44.json create mode 100644 .changes/1.21.45.json create mode 100644 .changes/1.21.46.json create mode 100644 .changes/1.21.5.json create mode 100644 .changes/1.21.6.json create mode 100644 .changes/1.21.7.json create mode 100644 .changes/1.21.8.json create mode 100644 .changes/1.21.9.json create mode 100644 .changes/1.22.0.json create mode 100644 .changes/1.22.1.json create mode 100644 .changes/1.22.10.json create mode 100644 .changes/1.22.11.json create mode 100644 .changes/1.22.12.json create mode 100644 .changes/1.22.13.json create mode 100644 .changes/1.22.2.json create mode 100644 .changes/1.22.3.json create mode 100644 .changes/1.22.4.json create mode 100644 .changes/1.22.5.json create mode 100644 .changes/1.22.6.json create mode 100644 .changes/1.22.7.json create mode 100644 .changes/1.22.8.json create mode 100644 .changes/1.22.9.json create mode 100644 .changes/1.23.0.json create mode 100644 .changes/1.23.1.json create mode 100644 .changes/1.23.2.json create mode 100644 .changes/1.23.3.json create mode 100644 .changes/1.23.4.json create mode 100644 .changes/1.23.5.json create mode 100644 .changes/1.23.6.json create mode 100644 .changes/1.23.7.json create mode 100644 .changes/1.23.8.json create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/ISSUE_TEMPLATE/guidance-issue.md create mode 100644 pyproject.toml diff --git a/.changes/1.20.35.json b/.changes/1.20.35.json new file mode 100644 index 0000000..a814c4a --- /dev/null +++ b/.changes/1.20.35.json @@ -0,0 +1,32 @@ +[ + { + "category": "``pinpoint``", + "description": "[``botocore``] Adds JourneyChannelSettings to WriteJourneyRequest", + "type": "api-change" + }, + { + "category": "``lexv2-runtime``", + "description": "[``botocore``] Update lexv2-runtime client to latest version", + "type": "api-change" + }, + { + "category": "``nimble``", + "description": "[``botocore``] Amazon Nimble Studio now supports validation for Launch Profiles. Launch Profiles now report static validation results after create/update to detect errors in network or active directory configuration.", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] This SDK release adds support to pass run properties when starting a workflow run", + "type": "api-change" + }, + { + "category": "``ssm``", + "description": "[``botocore``] AWS Systems Manager adds category support for DescribeDocument API", + "type": "api-change" + }, + { + "category": "``elasticache``", + "description": "[``botocore``] AWS ElastiCache for Redis has added a new Engine Log LogType in LogDelivery feature. You can now publish the Engine Log from your Amazon ElastiCache for Redis clusters to Amazon CloudWatch Logs and Amazon Kinesis Data Firehose.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.36.json b/.changes/1.20.36.json new file mode 100644 index 0000000..0f397d5 --- /dev/null +++ b/.changes/1.20.36.json @@ -0,0 +1,27 @@ +[ + { + "category": "``config``", + "description": "[``botocore``] Update ResourceType enum with values for CodeDeploy, EC2 and Kinesis resources", + "type": "api-change" + }, + { + "category": "``application-insights``", + "description": "[``botocore``] Application Insights support for Active Directory and SharePoint", + "type": "api-change" + }, + { + "category": "``honeycode``", + "description": "[``botocore``] Added read and write api support for multi-select picklist. And added errorcode field to DescribeTableDataImportJob API output, when import job fails.", + "type": "api-change" + }, + { + "category": "``ram``", + "description": "[``botocore``] This release adds the ListPermissionVersions API which lists the versions for a given permission.", + "type": "api-change" + }, + { + "category": "``lookoutmetrics``", + "description": "[``botocore``] This release adds a new DeactivateAnomalyDetector API operation.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.37.json b/.changes/1.20.37.json new file mode 100644 index 0000000..b2c87b9 --- /dev/null +++ b/.changes/1.20.37.json @@ -0,0 +1,7 @@ +[ + { + "category": "Configuration", + "description": "[``botocore``] Adding support for `defaults_mode` configuration. The `defaults_mode` will be used to determine how certain default configuration options are resolved in the SDK.", + "type": "enhancement" + } +] \ No newline at end of file diff --git a/.changes/1.20.38.json b/.changes/1.20.38.json new file mode 100644 index 0000000..d111076 --- /dev/null +++ b/.changes/1.20.38.json @@ -0,0 +1,27 @@ +[ + { + "category": "``ivs``", + "description": "[``botocore``] This release adds support for the new Thumbnail Configuration property for Recording Configurations. For more information see https://docs.aws.amazon.com/ivs/latest/userguide/record-to-s3.html", + "type": "api-change" + }, + { + "category": "``storagegateway``", + "description": "[``botocore``] Documentation update for adding bandwidth throttling support for S3 File Gateways.", + "type": "api-change" + }, + { + "category": "``location``", + "description": "[``botocore``] This release adds the CalculateRouteMatrix API which calculates routes for the provided departure and destination positions. The release also deprecates the use of pricing plan across all verticals.", + "type": "api-change" + }, + { + "category": "``cloudtrail``", + "description": "[``botocore``] This release fixes a documentation bug in the description for the readOnly field selector in advanced event selectors. The description now clarifies that users omit the readOnly field selector to select both Read and Write management events.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Add support for AWS Client VPN client login banner and session timeout.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.39.json b/.changes/1.20.39.json new file mode 100644 index 0000000..c027a09 --- /dev/null +++ b/.changes/1.20.39.json @@ -0,0 +1,12 @@ +[ + { + "category": "``macie2``", + "description": "[``botocore``] This release of the Amazon Macie API introduces stricter validation of requests to create custom data identifiers.", + "type": "api-change" + }, + { + "category": "``ec2-instance-connect``", + "description": "[``botocore``] Adds support for ED25519 keys. PushSSHPublicKey Availability Zone parameter is now optional. Adds EC2InstanceStateInvalidException for instances that are not running. This was previously a service exception, so this may require updating your code to handle this new exception.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.40.json b/.changes/1.20.40.json new file mode 100644 index 0000000..9133c23 --- /dev/null +++ b/.changes/1.20.40.json @@ -0,0 +1,27 @@ +[ + { + "category": "``guardduty``", + "description": "[``botocore``] Amazon GuardDuty findings now include remoteAccountDetails under AwsApiCallAction section if instance credential is exfiltrated.", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release adds tagging support for UserHierarchyGroups resource.", + "type": "api-change" + }, + { + "category": "``mediatailor``", + "description": "[``botocore``] This release adds support for multiple Segment Delivery Configurations. Users can provide a list of names and URLs when creating or editing a source location. When retrieving content, users can send a header to choose which URL should be used to serve content.", + "type": "api-change" + }, + { + "category": "``fis``", + "description": "[``botocore``] Added action startTime and action endTime timestamp fields to the ExperimentAction object", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] C6i, M6i and R6i instances are powered by a third-generation Intel Xeon Scalable processor (Ice Lake) delivering all-core turbo frequency of 3.5 GHz", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.41.json b/.changes/1.20.41.json new file mode 100644 index 0000000..9a08f9b --- /dev/null +++ b/.changes/1.20.41.json @@ -0,0 +1,17 @@ +[ + { + "category": "Exceptions", + "description": "[``botocore``] ProxyConnectionError previously provided the full proxy URL. User info will now be appropriately masked if needed.", + "type": "enhancement" + }, + { + "category": "``mediaconvert``", + "description": "[``botocore``] AWS Elemental MediaConvert SDK has added support for 4K AV1 output resolutions & 10-bit AV1 color, the ability to ingest sidecar Dolby Vision XML metadata files, and the ability to flag WebVTT and IMSC tracks for accessibility in HLS.", + "type": "api-change" + }, + { + "category": "``transcribe``", + "description": "[``botocore``] Add support for granular PIIEntityTypes when using Batch ContentRedaction.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.42.json b/.changes/1.20.42.json new file mode 100644 index 0000000..011dde6 --- /dev/null +++ b/.changes/1.20.42.json @@ -0,0 +1,7 @@ +[ + { + "category": "``route53-recovery-readiness``", + "description": "[``botocore``] Updated documentation for Route53 Recovery Readiness APIs.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.43.json b/.changes/1.20.43.json new file mode 100644 index 0000000..bdcf031 --- /dev/null +++ b/.changes/1.20.43.json @@ -0,0 +1,22 @@ +[ + { + "category": "``fsx``", + "description": "[``botocore``] This release adds support for growing SSD storage capacity and growing/shrinking SSD IOPS for FSx for ONTAP file systems.", + "type": "api-change" + }, + { + "category": "``efs``", + "description": "[``botocore``] Update efs client to latest version", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release adds support for custom vocabularies to be used with Contact Lens. Custom vocabularies improve transcription accuracy for one or more specific words.", + "type": "api-change" + }, + { + "category": "``guardduty``", + "description": "[``botocore``] Amazon GuardDuty expands threat detection coverage to protect Amazon Elastic Kubernetes Service (EKS) workloads.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.44.json b/.changes/1.20.44.json new file mode 100644 index 0000000..37be47c --- /dev/null +++ b/.changes/1.20.44.json @@ -0,0 +1,27 @@ +[ + { + "category": "``frauddetector``", + "description": "[``botocore``] Added new APIs for viewing past predictions and obtaining prediction metadata including prediction explanations: ListEventPredictions and GetEventPredictionMetadata", + "type": "api-change" + }, + { + "category": "``ebs``", + "description": "[``botocore``] Documentation updates for Amazon EBS Direct APIs.", + "type": "api-change" + }, + { + "category": "``codeguru-reviewer``", + "description": "[``botocore``] Added failure state and adjusted timeout in waiter", + "type": "api-change" + }, + { + "category": "``securityhub``", + "description": "[``botocore``] Adding top level Sample boolean field", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] API changes relating to Fail steps in model building pipeline and add PipelineExecutionFailureReason in PipelineExecutionSummary.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.45.json b/.changes/1.20.45.json new file mode 100644 index 0000000..1391293 --- /dev/null +++ b/.changes/1.20.45.json @@ -0,0 +1,27 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] X2ezn instances are powered by Intel Cascade Lake CPUs that deliver turbo all core frequency of up to 4.5 GHz and up to 100 Gbps of networking bandwidth", + "type": "api-change" + }, + { + "category": "``kafka``", + "description": "[``botocore``] Amazon MSK has updated the CreateCluster and UpdateBrokerStorage API that allows you to specify volume throughput during cluster creation and broker volume updates.", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release adds support for configuring a custom chat duration when starting a new chat session via the StartChatContact API. The default value for chat duration is 25 hours, minimum configurable value is 1 hour (60 minutes) and maximum configurable value is 7 days (10,080 minutes).", + "type": "api-change" + }, + { + "category": "``amplify``", + "description": "[``botocore``] Doc only update to the description of basicauthcredentials to describe the required encoding and format.", + "type": "api-change" + }, + { + "category": "``opensearch``", + "description": "[``botocore``] Allows customers to get progress updates for blue/green deployments", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.46.json b/.changes/1.20.46.json new file mode 100644 index 0000000..ebd1e0c --- /dev/null +++ b/.changes/1.20.46.json @@ -0,0 +1,32 @@ +[ + { + "category": "``appconfigdata``", + "description": "[``botocore``] Documentation updates for AWS AppConfig Data.", + "type": "api-change" + }, + { + "category": "``athena``", + "description": "[``botocore``] This release adds a field, AthenaError, to the GetQueryExecution response object when a query fails.", + "type": "api-change" + }, + { + "category": "``appconfig``", + "description": "[``botocore``] Documentation updates for AWS AppConfig", + "type": "api-change" + }, + { + "category": "``cognito-idp``", + "description": "[``botocore``] Doc updates for Cognito user pools API Reference.", + "type": "api-change" + }, + { + "category": "``secretsmanager``", + "description": "[``botocore``] Feature are ready to release on Jan 28th", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] This release added a new NNA accelerator compilation support for Sagemaker Neo.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.47.json b/.changes/1.20.47.json new file mode 100644 index 0000000..560436e --- /dev/null +++ b/.changes/1.20.47.json @@ -0,0 +1,62 @@ +[ + { + "category": "``emr``", + "description": "[``botocore``] Update emr client to latest version", + "type": "api-change" + }, + { + "category": "``personalize``", + "description": "[``botocore``] Adding minRecommendationRequestsPerSecond attribute to recommender APIs.", + "type": "api-change" + }, + { + "category": "Request headers", + "description": "[``botocore``] Adding request headers with retry information.", + "type": "enhancement" + }, + { + "category": "``appflow``", + "description": "[``botocore``] Launching Amazon AppFlow Custom Connector SDK.", + "type": "api-change" + }, + { + "category": "``dynamodb``", + "description": "[``botocore``] Documentation update for DynamoDB Java SDK.", + "type": "api-change" + }, + { + "category": "``iot``", + "description": "[``botocore``] This release adds support for configuring AWS IoT logging level per client ID, source IP, or principal ID.", + "type": "api-change" + }, + { + "category": "``comprehend``", + "description": "[``botocore``] Amazon Comprehend now supports sharing and importing custom trained models from one AWS account to another within the same region.", + "type": "api-change" + }, + { + "category": "``ce``", + "description": "[``botocore``] Doc-only update for Cost Explorer API that adds INVOICING_ENTITY dimensions", + "type": "api-change" + }, + { + "category": "``fis``", + "description": "[``botocore``] Added GetTargetResourceType and ListTargetResourceTypesAPI actions. These actions return additional details about resource types and parameters that can be targeted by FIS actions. Added a parameters field for the targets that can be specified in experiment templates.", + "type": "api-change" + }, + { + "category": "``es``", + "description": "[``botocore``] Allows customers to get progress updates for blue/green deployments", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] Launch Protobuf support for AWS Glue Schema Registry", + "type": "api-change" + }, + { + "category": "``elasticache``", + "description": "[``botocore``] Documentation update for AWS ElastiCache", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.48.json b/.changes/1.20.48.json new file mode 100644 index 0000000..ce7051d --- /dev/null +++ b/.changes/1.20.48.json @@ -0,0 +1,22 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] adds support for AMIs in Recycle Bin", + "type": "api-change" + }, + { + "category": "``robomaker``", + "description": "[``botocore``] The release deprecates the use various APIs of RoboMaker Deployment Service in favor of AWS IoT GreenGrass v2.0.", + "type": "api-change" + }, + { + "category": "``meteringmarketplace``", + "description": "[``botocore``] Add CustomerAWSAccountId to ResolveCustomer API response and increase UsageAllocation limit to 2500.", + "type": "api-change" + }, + { + "category": "``rbin``", + "description": "[``botocore``] Add EC2 Image recycle bin support.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.49.json b/.changes/1.20.49.json new file mode 100644 index 0000000..c3a3eea --- /dev/null +++ b/.changes/1.20.49.json @@ -0,0 +1,17 @@ +[ + { + "category": "``athena``", + "description": "[``botocore``] You can now optionally specify the account ID that you expect to be the owner of your query results output location bucket in Athena. If the account ID of the query results bucket owner does not match the specified account ID, attempts to output to the bucket will fail with an S3 permissions error.", + "type": "api-change" + }, + { + "category": "``rds``", + "description": "[``botocore``] updates for RDS Custom for Oracle 12.1 support", + "type": "api-change" + }, + { + "category": "``lakeformation``", + "description": "[``botocore``] Add support for calling Update Table Objects without a TransactionId.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.50.json b/.changes/1.20.50.json new file mode 100644 index 0000000..23bf29c --- /dev/null +++ b/.changes/1.20.50.json @@ -0,0 +1,27 @@ +[ + { + "category": "``auditmanager``", + "description": "[``botocore``] This release updates 3 API parameters. UpdateAssessmentFrameworkControlSet now requires the controls attribute, and CreateAssessmentFrameworkControl requires the id attribute. Additionally, UpdateAssessmentFramework now has a minimum length constraint for the controlSets attribute.", + "type": "api-change" + }, + { + "category": "``synthetics``", + "description": "[``botocore``] Adding names parameters to the Describe APIs.", + "type": "api-change" + }, + { + "category": "``ssm-incidents``", + "description": "[``botocore``] Update RelatedItem enum to support SSM Automation", + "type": "api-change" + }, + { + "category": "``events``", + "description": "[``botocore``] Update events client to latest version", + "type": "api-change" + }, + { + "category": "Lambda Request Header", + "description": "[``botocore``] Adding request header for Lambda recursion detection.", + "type": "enhancement" + } +] \ No newline at end of file diff --git a/.changes/1.20.51.json b/.changes/1.20.51.json new file mode 100644 index 0000000..d3694f4 --- /dev/null +++ b/.changes/1.20.51.json @@ -0,0 +1,22 @@ +[ + { + "category": "``kendra``", + "description": "[``botocore``] Amazon Kendra now provides a data source connector for Amazon FSx. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-fsx.html", + "type": "api-change" + }, + { + "category": "``apprunner``", + "description": "[``botocore``] This release adds support for App Runner to route outbound network traffic of a service through an Amazon VPC. New API: CreateVpcConnector, DescribeVpcConnector, ListVpcConnectors, and DeleteVpcConnector. Updated API: CreateService, DescribeService, and UpdateService.", + "type": "api-change" + }, + { + "category": "``s3control``", + "description": "[``botocore``] This release adds support for S3 Batch Replication. Batch Replication lets you replicate existing objects, already replicated objects to new destinations, and objects that previously failed to replicate. Customers will receive object-level visibility of progress and a detailed completion report.", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] Autopilot now generates an additional report with information on the performance of the best model, such as a Confusion matrix and Area under the receiver operating characteristic (AUC-ROC). The path to the report can be found in CandidateArtifactLocations.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.52.json b/.changes/1.20.52.json new file mode 100644 index 0000000..90d07ef --- /dev/null +++ b/.changes/1.20.52.json @@ -0,0 +1,7 @@ +[ + { + "category": "``cloudformation``", + "description": "[``botocore``] This SDK release is for the feature launch of AWS CloudFormation Hooks.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.53.json b/.changes/1.20.53.json new file mode 100644 index 0000000..fb6fe38 --- /dev/null +++ b/.changes/1.20.53.json @@ -0,0 +1,27 @@ +[ + { + "category": "``cloudformation``", + "description": "[``botocore``] This SDK release adds AWS CloudFormation Hooks HandlerErrorCodes", + "type": "api-change" + }, + { + "category": "``lookoutvision``", + "description": "[``botocore``] This release makes CompilerOptions in Lookout for Vision's StartModelPackagingJob's Configuration object optional.", + "type": "api-change" + }, + { + "category": "``pinpoint``", + "description": "[``botocore``] This SDK release adds a new paramater creation date for GetApp and GetApps Api call", + "type": "api-change" + }, + { + "category": "``sns``", + "description": "[``botocore``] Customer requested typo fix in API documentation.", + "type": "api-change" + }, + { + "category": "``wafv2``", + "description": "[``botocore``] Adds support for AWS WAF Fraud Control account takeover prevention (ATP), with configuration options for the new managed rule group AWSManagedRulesATPRuleSet and support for application integration SDKs for Android and iOS mobile apps.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.20.54.json b/.changes/1.20.54.json new file mode 100644 index 0000000..1b16559 --- /dev/null +++ b/.changes/1.20.54.json @@ -0,0 +1,7 @@ +[ + { + "category": "``ssm``", + "description": "[``botocore``] Documentation updates for AWS Systems Manager.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.0.json b/.changes/1.21.0.json new file mode 100644 index 0000000..ea20d0f --- /dev/null +++ b/.changes/1.21.0.json @@ -0,0 +1,22 @@ +[ + { + "category": "``appflow``", + "description": "[``botocore``] Launching Amazon AppFlow SAP as a destination connector SDK.", + "type": "api-change" + }, + { + "category": "Parser", + "description": "[``botocore``] Adding support for parsing int/long types in rest-json response headers.", + "type": "feature" + }, + { + "category": "``rds``", + "description": "[``botocore``] Adds support for determining which Aurora PostgreSQL versions support Babelfish.", + "type": "api-change" + }, + { + "category": "``athena``", + "description": "[``botocore``] This release adds a subfield, ErrorType, to the AthenaError response object in the GetQueryExecution API when a query fails.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.1.json b/.changes/1.21.1.json new file mode 100644 index 0000000..2f603a9 --- /dev/null +++ b/.changes/1.21.1.json @@ -0,0 +1,27 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] Documentation updates for EC2.", + "type": "api-change" + }, + { + "category": "``budgets``", + "description": "[``botocore``] Adds support for auto-adjusting budgets, a new budget method alongside fixed and planned. Auto-adjusting budgets introduces new metadata to configure a budget limit baseline using a historical lookback average or current period forecast.", + "type": "api-change" + }, + { + "category": "``ce``", + "description": "[``botocore``] AWS Cost Anomaly Detection now supports SNS FIFO topic subscribers.", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] Support for optimistic locking in UpdateTable", + "type": "api-change" + }, + { + "category": "``ssm``", + "description": "[``botocore``] Assorted ticket fixes and updates for AWS Systems Manager.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.10.json b/.changes/1.21.10.json new file mode 100644 index 0000000..dfcdff7 --- /dev/null +++ b/.changes/1.21.10.json @@ -0,0 +1,22 @@ +[ + { + "category": "``mediapackage``", + "description": "[``botocore``] This release adds Hybridcast as an available profile option for Dash Origin Endpoints.", + "type": "api-change" + }, + { + "category": "``rds``", + "description": "[``botocore``] Documentation updates for Multi-AZ DB clusters.", + "type": "api-change" + }, + { + "category": "``mgn``", + "description": "[``botocore``] Add support for GP3 and IO2 volume types. Add bootMode to LaunchConfiguration object (and as a parameter to UpdateLaunchConfigurationRequest).", + "type": "api-change" + }, + { + "category": "``kafkaconnect``", + "description": "[``botocore``] Adds operation for custom plugin deletion (DeleteCustomPlugin) and adds new StateDescription field to DescribeCustomPlugin and DescribeConnector responses to return errors from asynchronous resource creation.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.11.json b/.changes/1.21.11.json new file mode 100644 index 0000000..e98b6db --- /dev/null +++ b/.changes/1.21.11.json @@ -0,0 +1,27 @@ +[ + { + "category": "``gamelift``", + "description": "[``botocore``] Minor updates to address errors.", + "type": "api-change" + }, + { + "category": "``cloudtrail``", + "description": "[``botocore``] Add bytesScanned field into responses of DescribeQuery and GetQueryResults.", + "type": "api-change" + }, + { + "category": "``athena``", + "description": "[``botocore``] This release adds support for S3 Object Ownership by allowing the S3 bucket owner full control canned ACL to be set when Athena writes query results to S3 buckets.", + "type": "api-change" + }, + { + "category": "``keyspaces``", + "description": "[``botocore``] This release adds support for data definition language (DDL) operations", + "type": "api-change" + }, + { + "category": "``ecr``", + "description": "[``botocore``] This release adds support for tracking images lastRecordedPullTime.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.12.json b/.changes/1.21.12.json new file mode 100644 index 0000000..efa8bf5 --- /dev/null +++ b/.changes/1.21.12.json @@ -0,0 +1,27 @@ +[ + { + "category": "``greengrassv2``", + "description": "[``botocore``] Doc only update that clarifies Create Deployment section.", + "type": "api-change" + }, + { + "category": "``fsx``", + "description": "[``botocore``] This release adds support for data repository associations to use root (\"/\") as the file system path", + "type": "api-change" + }, + { + "category": "``kendra``", + "description": "[``botocore``] Amazon Kendra now suggests spell corrections for a query. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/query-spell-check.html", + "type": "api-change" + }, + { + "category": "``appflow``", + "description": "[``botocore``] Launching Amazon AppFlow Marketo as a destination connector SDK.", + "type": "api-change" + }, + { + "category": "``timestream-query``", + "description": "[``botocore``] Documentation only update for SDK and CLI", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.13.json b/.changes/1.21.13.json new file mode 100644 index 0000000..409f507 --- /dev/null +++ b/.changes/1.21.13.json @@ -0,0 +1,37 @@ +[ + { + "category": "``synthetics``", + "description": "[``botocore``] Allow custom handler function.", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] Add waiters for server online and offline.", + "type": "api-change" + }, + { + "category": "``devops-guru``", + "description": "[``botocore``] Amazon DevOps Guru now integrates with Amazon CodeGuru Profiler. You can view CodeGuru Profiler recommendations for your AWS Lambda function in DevOps Guru. This feature is enabled by default for new customers as of 3/4/2022. Existing customers can enable this feature with UpdateEventSourcesConfig.", + "type": "api-change" + }, + { + "category": "``macie``", + "description": "[``botocore``] Amazon Macie Classic (macie) has been discontinued and is no longer available. A new Amazon Macie (macie2) is now available with significant design improvements and additional features.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Documentation updates for Amazon EC2.", + "type": "api-change" + }, + { + "category": "``sts``", + "description": "[``botocore``] Documentation updates for AWS Security Token Service.", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release updates the *InstanceStorageConfig APIs so they support a new ResourceType: REAL_TIME_CONTACT_ANALYSIS_SEGMENTS. Use this resource type to enable streaming for real-time contact analysis and to associate the Kinesis stream where real-time contact analysis segments will be published.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.14.json b/.changes/1.21.14.json new file mode 100644 index 0000000..32a6e97 --- /dev/null +++ b/.changes/1.21.14.json @@ -0,0 +1,17 @@ +[ + { + "category": "``chime-sdk-meetings``", + "description": "[``botocore``] Adds support for Transcribe language identification feature to the StartMeetingTranscription API.", + "type": "api-change" + }, + { + "category": "``ecs``", + "description": "[``botocore``] Amazon ECS UpdateService API now supports additional parameters: loadBalancers, propagateTags, enableECSManagedTags, and serviceRegistries", + "type": "api-change" + }, + { + "category": "``migration-hub-refactor-spaces``", + "description": "[``botocore``] AWS Migration Hub Refactor Spaces documentation update.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.15.json b/.changes/1.21.15.json new file mode 100644 index 0000000..86cd999 --- /dev/null +++ b/.changes/1.21.15.json @@ -0,0 +1,17 @@ +[ + { + "category": "``eks``", + "description": "[``botocore``] Introducing a new enum for NodeGroup error code: Ec2SubnetMissingIpv6Assignment", + "type": "api-change" + }, + { + "category": "``keyspaces``", + "description": "[``botocore``] Adding link to CloudTrail section in Amazon Keyspaces Developer Guide", + "type": "api-change" + }, + { + "category": "``mediaconvert``", + "description": "[``botocore``] AWS Elemental MediaConvert SDK has added support for reading timecode from AVCHD sources and now provides the ability to segment WebVTT at the same interval as the video and audio in HLS packages.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.16.json b/.changes/1.21.16.json new file mode 100644 index 0000000..153e79e --- /dev/null +++ b/.changes/1.21.16.json @@ -0,0 +1,7 @@ +[ + { + "category": "``comprehend``", + "description": "[``botocore``] Amazon Comprehend now supports extracting the sentiment associated with entities such as brands, products and services from text documents.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.17.json b/.changes/1.21.17.json new file mode 100644 index 0000000..4a6ebd8 --- /dev/null +++ b/.changes/1.21.17.json @@ -0,0 +1,17 @@ +[ + { + "category": "``transcribe``", + "description": "[``botocore``] Documentation fix for API `StartMedicalTranscriptionJobRequest`, now showing min sample rate as 16khz", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] Adding more descriptive error types for managed workflows", + "type": "api-change" + }, + { + "category": "``lexv2-models``", + "description": "[``botocore``] Update lexv2-models client to latest version", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.18.json b/.changes/1.21.18.json new file mode 100644 index 0000000..fdbaea0 --- /dev/null +++ b/.changes/1.21.18.json @@ -0,0 +1,27 @@ +[ + { + "category": "``outposts``", + "description": "[``botocore``] This release adds address filters for listSites", + "type": "api-change" + }, + { + "category": "``lambda``", + "description": "[``botocore``] Adds PrincipalOrgID support to AddPermission API. Customers can use it to manage permissions to lambda functions at AWS Organizations level.", + "type": "api-change" + }, + { + "category": "``secretsmanager``", + "description": "[``botocore``] Documentation updates for Secrets Manager.", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release adds support for enabling Rich Messaging when starting a new chat session via the StartChatContact API. Rich Messaging enables the following formatting options: bold, italics, hyperlinks, bulleted lists, and numbered lists.", + "type": "api-change" + }, + { + "category": "``chime``", + "description": "[``botocore``] Chime VoiceConnector Logging APIs will now support MediaMetricLogs. Also CreateMeetingDialOut now returns AccessDeniedException.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.19.json b/.changes/1.21.19.json new file mode 100644 index 0000000..07aa521 --- /dev/null +++ b/.changes/1.21.19.json @@ -0,0 +1,27 @@ +[ + { + "category": "``kendra``", + "description": "[``botocore``] Amazon Kendra now provides a data source connector for Slack. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-slack.html", + "type": "api-change" + }, + { + "category": "``timestream-query``", + "description": "[``botocore``] Amazon Timestream Scheduled Queries now support Timestamp datatype in a multi-measure record.", + "type": "api-change" + }, + { + "category": "Stubber", + "description": "[``botocore``] Added support for modeled exception fields when adding errors to a client stub. Implements boto/boto3`#3178 `__.", + "type": "enhancement" + }, + { + "category": "``elasticache``", + "description": "[``botocore``] Doc only update for ElastiCache", + "type": "api-change" + }, + { + "category": "``config``", + "description": "[``botocore``] Add resourceType enums for AWS::ECR::PublicRepository and AWS::EC2::LaunchTemplate", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.2.json b/.changes/1.21.2.json new file mode 100644 index 0000000..5908dad --- /dev/null +++ b/.changes/1.21.2.json @@ -0,0 +1,22 @@ +[ + { + "category": "``iam``", + "description": "[``botocore``] Documentation updates for AWS Identity and Access Management (IAM).", + "type": "api-change" + }, + { + "category": "``redshift``", + "description": "[``botocore``] SDK release for Cross region datasharing and cost-control for cross region datasharing", + "type": "api-change" + }, + { + "category": "``evidently``", + "description": "[``botocore``] Add support for filtering list of experiments and launches by status", + "type": "api-change" + }, + { + "category": "``backup``", + "description": "[``botocore``] AWS Backup add new S3_BACKUP_OBJECT_FAILED and S3_RESTORE_OBJECT_FAILED event types in BackupVaultNotifications events list.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.20.json b/.changes/1.21.20.json new file mode 100644 index 0000000..e8b6feb --- /dev/null +++ b/.changes/1.21.20.json @@ -0,0 +1,47 @@ +[ + { + "category": "``robomaker``", + "description": "[``botocore``] This release deprecates ROS, Ubuntu and Gazbeo from RoboMaker Simulation Service Software Suites in favor of user-supplied containers and Relaxed Software Suites.", + "type": "api-change" + }, + { + "category": "``dataexchange``", + "description": "[``botocore``] This feature enables data providers to use the RevokeRevision operation to revoke subscriber access to a given revision. Subscribers are unable to interact with assets within a revoked revision.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Adds the Cascade parameter to the DeleteIpam API. Customers can use this parameter to automatically delete their IPAM, including non-default scopes, pools, cidrs, and allocations. There mustn't be any pools provisioned in the default public scope to use this parameter.", + "type": "api-change" + }, + { + "category": "``cognito-idp``", + "description": "[``botocore``] Updated EmailConfigurationType and SmsConfigurationType to reflect that you can now choose Amazon SES and Amazon SNS resources in the same Region.", + "type": "api-change" + }, + { + "category": "AWSCRT", + "description": "[``botocore``] Upgrade awscrt extra to 0.13.5", + "type": "enhancement" + }, + { + "category": "``location``", + "description": "[``botocore``] New HERE style \"VectorHereExplore\" and \"VectorHereExploreTruck\".", + "type": "api-change" + }, + { + "category": "``ecs``", + "description": "[``botocore``] Documentation only update to address tickets", + "type": "api-change" + }, + { + "category": "``keyspaces``", + "description": "[``botocore``] Fixing formatting issues in CLI and SDK documentation", + "type": "api-change" + }, + { + "category": "``rds``", + "description": "[``botocore``] Various documentation improvements", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.21.json b/.changes/1.21.21.json new file mode 100644 index 0000000..2d09a4c --- /dev/null +++ b/.changes/1.21.21.json @@ -0,0 +1,37 @@ +[ + { + "category": "Dependency", + "description": "[``botocore``] Added support for jmespath 1.0", + "type": "enhancement" + }, + { + "category": "``amplifybackend``", + "description": "[``botocore``] Adding the ability to customize Cognito verification messages for email and SMS in CreateBackendAuth and UpdateBackendAuth. Adding deprecation documentation for ForgotPassword in CreateBackendAuth and UpdateBackendAuth", + "type": "api-change" + }, + { + "category": "``acm-pca``", + "description": "[``botocore``] AWS Certificate Manager (ACM) Private Certificate Authority (CA) now supports customizable certificate subject names and extensions.", + "type": "api-change" + }, + { + "category": "``ssm-incidents``", + "description": "[``botocore``] Removed incorrect validation pattern for IncidentRecordSource.invokedBy", + "type": "api-change" + }, + { + "category": "Dependency", + "description": "Added support for jmespath 1.0", + "type": "enhancement" + }, + { + "category": "``billingconductor``", + "description": "[``botocore``] This is the initial SDK release for AWS Billing Conductor. The AWS Billing Conductor is a customizable billing service, allowing you to customize your billing data to match your desired business structure.", + "type": "api-change" + }, + { + "category": "``s3outposts``", + "description": "[``botocore``] S3 on Outposts is releasing a new API, ListSharedEndpoints, that lists all endpoints associated with S3 on Outpost, that has been shared by Resource Access Manager (RAM).", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.22.json b/.changes/1.21.22.json new file mode 100644 index 0000000..892b906 --- /dev/null +++ b/.changes/1.21.22.json @@ -0,0 +1,12 @@ +[ + { + "category": "jmespath", + "description": "[``botocore``] Add env markers to get working version of jmespath for python 3.6", + "type": "enhancement" + }, + { + "category": "``glue``", + "description": "[``botocore``] Added 9 new APIs for AWS Glue Interactive Sessions: ListSessions, StopSession, CreateSession, GetSession, DeleteSession, RunStatement, GetStatement, ListStatements, CancelStatement", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.23.json b/.changes/1.21.23.json new file mode 100644 index 0000000..7847329 --- /dev/null +++ b/.changes/1.21.23.json @@ -0,0 +1,27 @@ +[ + { + "category": "``ram``", + "description": "[``botocore``] Document improvements to the RAM API operations and parameter descriptions.", + "type": "api-change" + }, + { + "category": "``ecr``", + "description": "[``botocore``] This release includes a fix in the DescribeImageScanFindings paginated output.", + "type": "api-change" + }, + { + "category": "``quicksight``", + "description": "[``botocore``] AWS QuickSight Service Features - Expand public API support for group management.", + "type": "api-change" + }, + { + "category": "``chime-sdk-meetings``", + "description": "[``botocore``] Add support for media replication to link multiple WebRTC media sessions together to reach larger and global audiences. Participants connected to a replica session can be granted access to join the primary session and can switch sessions with their existing WebRTC connection", + "type": "api-change" + }, + { + "category": "``mediaconnect``", + "description": "[``botocore``] This release adds support for selecting a maintenance window.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.24.json b/.changes/1.21.24.json new file mode 100644 index 0000000..204bd3b --- /dev/null +++ b/.changes/1.21.24.json @@ -0,0 +1,27 @@ +[ + { + "category": "``location``", + "description": "[``botocore``] Amazon Location Service now includes a MaxResults parameter for GetDevicePositionHistory requests.", + "type": "api-change" + }, + { + "category": "``polly``", + "description": "[``botocore``] Amazon Polly adds new Catalan voice - Arlet. Arlet is available as Neural voice only.", + "type": "api-change" + }, + { + "category": "``lakeformation``", + "description": "[``botocore``] The release fixes the incorrect permissions called out in the documentation - DESCRIBE_TAG, ASSOCIATE_TAG, DELETE_TAG, ALTER_TAG. This trebuchet release fixes the corresponding SDK and documentation.", + "type": "api-change" + }, + { + "category": "``ecs``", + "description": "[``botocore``] Documentation only update to address tickets", + "type": "api-change" + }, + { + "category": "``ce``", + "description": "[``botocore``] Added three new APIs to support tagging and resource-level authorization on Cost Explorer resources: TagResource, UntagResource, ListTagsForResource. Added optional parameters to CreateCostCategoryDefinition, CreateAnomalySubscription and CreateAnomalyMonitor APIs to support Tag On Create.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.25.json b/.changes/1.21.25.json new file mode 100644 index 0000000..3121192 --- /dev/null +++ b/.changes/1.21.25.json @@ -0,0 +1,37 @@ +[ + { + "category": "``redshift``", + "description": "[``botocore``] This release adds a new [--encrypted | --no-encrypted] field in restore-from-cluster-snapshot API. Customers can now restore an unencrypted snapshot to a cluster encrypted with AWS Managed Key or their own KMS key.", + "type": "api-change" + }, + { + "category": "``ebs``", + "description": "[``botocore``] Increased the maximum supported value for the Timeout parameter of the StartSnapshot API from 60 minutes to 4320 minutes. Changed the HTTP error code for ConflictException from 503 to 409.", + "type": "api-change" + }, + { + "category": "``gamesparks``", + "description": "[``botocore``] Released the preview of Amazon GameSparks, a fully managed AWS service that provides a multi-service backend for game developers.", + "type": "api-change" + }, + { + "category": "``elasticache``", + "description": "[``botocore``] Doc only update for ElastiCache", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] Documentation updates for AWS Transfer Family to describe how to remove an associated workflow from a server.", + "type": "api-change" + }, + { + "category": "``auditmanager``", + "description": "[``botocore``] This release updates 1 API parameter, the SnsArn attribute. The character length and regex pattern for the SnsArn attribute have been updated, which enables you to deselect an SNS topic when using the UpdateSettings operation.", + "type": "api-change" + }, + { + "category": "``ssm``", + "description": "[``botocore``] Update AddTagsToResource, ListTagsForResource, and RemoveTagsFromResource APIs to reflect the support for tagging Automation resources. Includes other minor documentation updates.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.26.json b/.changes/1.21.26.json new file mode 100644 index 0000000..8265ab9 --- /dev/null +++ b/.changes/1.21.26.json @@ -0,0 +1,17 @@ +[ + { + "category": "``lambda``", + "description": "[``botocore``] Adds support for increased ephemeral storage (/tmp) up to 10GB for Lambda functions. Customers can now provision up to 10 GB of ephemeral storage per function instance, a 20x increase over the previous limit of 512 MB.", + "type": "api-change" + }, + { + "category": "``config``", + "description": "[``botocore``] Added new APIs GetCustomRulePolicy and GetOrganizationCustomRulePolicy, and updated existing APIs PutConfigRule, DescribeConfigRule, DescribeConfigRuleEvaluationStatus, PutOrganizationConfigRule, DescribeConfigRule to support a new feature for building AWS Config rules with AWS CloudFormation Guard", + "type": "api-change" + }, + { + "category": "``transcribe``", + "description": "[``botocore``] This release adds an additional parameter for subtitling with Amazon Transcribe batch jobs: outputStartIndex.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.27.json b/.changes/1.21.27.json new file mode 100644 index 0000000..928cc4a --- /dev/null +++ b/.changes/1.21.27.json @@ -0,0 +1,17 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] This is release adds support for Amazon VPC Reachability Analyzer to analyze path through a Transit Gateway.", + "type": "api-change" + }, + { + "category": "``ssm``", + "description": "[``botocore``] This Patch Manager release supports creating, updating, and deleting Patch Baselines for Rocky Linux OS.", + "type": "api-change" + }, + { + "category": "``batch``", + "description": "[``botocore``] Bug Fix: Fixed a bug where shapes were marked as unboxed and were not serialized and sent over the wire, causing an API error from the service.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.28.json b/.changes/1.21.28.json new file mode 100644 index 0000000..d4ff0aa --- /dev/null +++ b/.changes/1.21.28.json @@ -0,0 +1,12 @@ +[ + { + "category": "``medialive``", + "description": "[``botocore``] This release adds support for selecting a maintenance window.", + "type": "api-change" + }, + { + "category": "``acm-pca``", + "description": "[``botocore``] Updating service name entities", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.29.json b/.changes/1.21.29.json new file mode 100644 index 0000000..9baf060 --- /dev/null +++ b/.changes/1.21.29.json @@ -0,0 +1,7 @@ +[ + { + "category": "``organizations``", + "description": "[``botocore``] This release provides the new CloseAccount API that enables principals in the management account to close any member account within an organization.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.3.json b/.changes/1.21.3.json new file mode 100644 index 0000000..e89b880 --- /dev/null +++ b/.changes/1.21.3.json @@ -0,0 +1,17 @@ +[ + { + "category": "``transfer``", + "description": "[``botocore``] Properties for Transfer Family used with SFTP, FTP, and FTPS protocols. Display Banners are bodies of text that can be displayed before and/or after a user authenticates onto a server using one of the previously mentioned protocols.", + "type": "api-change" + }, + { + "category": "``gamelift``", + "description": "[``botocore``] Increase string list limit from 10 to 100.", + "type": "api-change" + }, + { + "category": "``budgets``", + "description": "[``botocore``] This change introduces DescribeBudgetNotificationsForAccount API which returns budget notifications for the specified account", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.30.json b/.changes/1.21.30.json new file mode 100644 index 0000000..096f893 --- /dev/null +++ b/.changes/1.21.30.json @@ -0,0 +1,27 @@ +[ + { + "category": "``iot-data``", + "description": "[``botocore``] Update the default AWS IoT Core Data Plane endpoint from VeriSign signed to ATS signed. If you have firewalls with strict egress rules, configure the rules to grant you access to data-ats.iot.[region].amazonaws.com or data-ats.iot.[region].amazonaws.com.cn.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] This release simplifies the auto-recovery configuration process enabling customers to set the recovery behavior to disabled or default", + "type": "api-change" + }, + { + "category": "``fms``", + "description": "[``botocore``] AWS Firewall Manager now supports the configuration of third-party policies that can use either the centralized or distributed deployment models.", + "type": "api-change" + }, + { + "category": "``fsx``", + "description": "[``botocore``] This release adds support for modifying throughput capacity for FSx for ONTAP file systems.", + "type": "api-change" + }, + { + "category": "``iot``", + "description": "[``botocore``] Doc only update for IoT that fixes customer-reported issues.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.31.json b/.changes/1.21.31.json new file mode 100644 index 0000000..f2520ef --- /dev/null +++ b/.changes/1.21.31.json @@ -0,0 +1,37 @@ +[ + { + "category": "``cloudcontrol``", + "description": "[``botocore``] SDK release for Cloud Control API in Amazon Web Services China (Beijing) Region, operated by Sinnet, and Amazon Web Services China (Ningxia) Region, operated by NWCD", + "type": "api-change" + }, + { + "category": "``pinpoint-sms-voice-v2``", + "description": "[``botocore``] Amazon Pinpoint now offers a version 2.0 suite of SMS and voice APIs, providing increased control over sending and configuration. This release is a new SDK for sending SMS and voice messages called PinpointSMSVoiceV2.", + "type": "api-change" + }, + { + "category": "``workspaces``", + "description": "[``botocore``] Added APIs that allow you to customize the logo, login message, and help links in the WorkSpaces client login page. To learn more, visit https://docs.aws.amazon.com/workspaces/latest/adminguide/customize-branding.html", + "type": "api-change" + }, + { + "category": "``route53-recovery-cluster``", + "description": "[``botocore``] This release adds a new API \"ListRoutingControls\" to list routing control states using the highly reliable Route 53 ARC data plane endpoints.", + "type": "api-change" + }, + { + "category": "``databrew``", + "description": "[``botocore``] This AWS Glue Databrew release adds feature to support ORC as an input format.", + "type": "api-change" + }, + { + "category": "``auditmanager``", + "description": "[``botocore``] This release adds documentation updates for Audit Manager. The updates provide data deletion guidance when a customer deregisters Audit Manager or deregisters a delegated administrator.", + "type": "api-change" + }, + { + "category": "``grafana``", + "description": "[``botocore``] This release adds tagging support to the Managed Grafana service. New APIs: TagResource, UntagResource and ListTagsForResource. Updates: add optional field tags to support tagging while calling CreateWorkspace.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.32.json b/.changes/1.21.32.json new file mode 100644 index 0000000..56afdeb --- /dev/null +++ b/.changes/1.21.32.json @@ -0,0 +1,7 @@ +[ + { + "category": "``connect``", + "description": "[``botocore``] This release updates these APIs: UpdateInstanceAttribute, DescribeInstanceAttribute and ListInstanceAttributes. You can use it to programmatically enable/disable multi-party conferencing using attribute type MULTI_PARTY_CONFERENCING on the specified Amazon Connect instance.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.33.json b/.changes/1.21.33.json new file mode 100644 index 0000000..0246052 --- /dev/null +++ b/.changes/1.21.33.json @@ -0,0 +1,27 @@ +[ + { + "category": "``iot``", + "description": "[``botocore``] AWS IoT - AWS IoT Device Defender adds support to list metric datapoints collected for IoT devices through the ListMetricValues API", + "type": "api-change" + }, + { + "category": "``servicecatalog``", + "description": "[``botocore``] This release adds ProvisioningArtifictOutputKeys to DescribeProvisioningParameters to reference the outputs of a Provisioned Product and deprecates ProvisioningArtifactOutputs.", + "type": "api-change" + }, + { + "category": "``sms``", + "description": "[``botocore``] Revised product update notice for SMS console deprecation.", + "type": "api-change" + }, + { + "category": "``proton``", + "description": "[``botocore``] SDK release to support tagging for AWS Proton Repository resource", + "type": "api-change" + }, + { + "category": "AWSCRT", + "description": "[``botocore``] Upgrade awscrt version to 0.13.8", + "type": "enhancement" + } +] \ No newline at end of file diff --git a/.changes/1.21.34.json b/.changes/1.21.34.json new file mode 100644 index 0000000..9b93c43 --- /dev/null +++ b/.changes/1.21.34.json @@ -0,0 +1,22 @@ +[ + { + "category": "``securityhub``", + "description": "[``botocore``] Added additional ASFF details for RdsSecurityGroup AutoScalingGroup, ElbLoadBalancer, CodeBuildProject and RedshiftCluster.", + "type": "api-change" + }, + { + "category": "``fsx``", + "description": "[``botocore``] Provide customers more visibility into file system status by adding new \"Misconfigured Unavailable\" status for Amazon FSx for Windows File Server.", + "type": "api-change" + }, + { + "category": "``s3control``", + "description": "[``botocore``] Documentation-only update for doc bug fixes for the S3 Control API docs.", + "type": "api-change" + }, + { + "category": "``datasync``", + "description": "[``botocore``] AWS DataSync now supports Amazon FSx for OpenZFS locations.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.35.json b/.changes/1.21.35.json new file mode 100644 index 0000000..66a1c9b --- /dev/null +++ b/.changes/1.21.35.json @@ -0,0 +1,27 @@ +[ + { + "category": "Proxy", + "description": "[``botocore``] Fix failure case for IP proxy addresses using TLS-in-TLS. `boto/botocore#2652 `__", + "type": "bugfix" + }, + { + "category": "``config``", + "description": "[``botocore``] Add resourceType enums for AWS::EMR::SecurityConfiguration and AWS::SageMaker::CodeRepository", + "type": "api-change" + }, + { + "category": "``panorama``", + "description": "[``botocore``] Added Brand field to device listings.", + "type": "api-change" + }, + { + "category": "``lambda``", + "description": "[``botocore``] This release adds new APIs for creating and managing Lambda Function URLs and adds a new FunctionUrlAuthType parameter to the AddPermission API. Customers can use Function URLs to create built-in HTTPS endpoints on their functions.", + "type": "api-change" + }, + { + "category": "``kendra``", + "description": "[``botocore``] Amazon Kendra now provides a data source connector for Box. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-box.html", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.36.json b/.changes/1.21.36.json new file mode 100644 index 0000000..22ee6bb --- /dev/null +++ b/.changes/1.21.36.json @@ -0,0 +1,32 @@ +[ + { + "category": "``apigateway``", + "description": "[``botocore``] ApiGateway CLI command get-usage now includes usagePlanId, startDate, and endDate fields in the output to match documentation.", + "type": "api-change" + }, + { + "category": "``personalize``", + "description": "[``botocore``] This release provides tagging support in AWS Personalize.", + "type": "api-change" + }, + { + "category": "``pi``", + "description": "[``botocore``] Adds support for DocumentDB to the Performance Insights API.", + "type": "api-change" + }, + { + "category": "``events``", + "description": "[``botocore``] Update events client to latest version", + "type": "api-change" + }, + { + "category": "``docdb``", + "description": "[``botocore``] Added support to enable/disable performance insights when creating or modifying db instances", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] Amazon Sagemaker Notebook Instances now supports G5 instance types", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.37.json b/.changes/1.21.37.json new file mode 100644 index 0000000..0fb191f --- /dev/null +++ b/.changes/1.21.37.json @@ -0,0 +1,17 @@ +[ + { + "category": "``mediaconvert``", + "description": "[``botocore``] AWS Elemental MediaConvert SDK has added support for the pass-through of WebVTT styling to WebVTT outputs, pass-through of KLV metadata to supported formats, and improved filter support for processing 444/RGB content.", + "type": "api-change" + }, + { + "category": "``wafv2``", + "description": "[``botocore``] Add a new CurrentDefaultVersion field to ListAvailableManagedRuleGroupVersions API response; add a new VersioningSupported boolean to each ManagedRuleGroup returned from ListAvailableManagedRuleGroups API response.", + "type": "api-change" + }, + { + "category": "``mediapackage-vod``", + "description": "[``botocore``] This release adds ScteMarkersSource as an available field for Dash Packaging Configurations. When set to MANIFEST, MediaPackage will source the SCTE-35 markers from the manifest. When set to SEGMENTS, MediaPackage will source the SCTE-35 markers from the segments.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.38.json b/.changes/1.21.38.json new file mode 100644 index 0000000..f2e95d3 --- /dev/null +++ b/.changes/1.21.38.json @@ -0,0 +1,17 @@ +[ + { + "category": "``amplifyuibuilder``", + "description": "[``botocore``] In this release, we have added the ability to bind events to component level actions.", + "type": "api-change" + }, + { + "category": "``apprunner``", + "description": "[``botocore``] This release adds tracing for App Runner services with X-Ray using AWS Distro for OpenTelemetry. New APIs: CreateObservabilityConfiguration, DescribeObservabilityConfiguration, ListObservabilityConfigurations, and DeleteObservabilityConfiguration. Updated APIs: CreateService and UpdateService.", + "type": "api-change" + }, + { + "category": "``workspaces``", + "description": "[``botocore``] Added API support that allows customers to create GPU-enabled WorkSpaces using EC2 G4dn instances.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.39.json b/.changes/1.21.39.json new file mode 100644 index 0000000..5dd81e7 --- /dev/null +++ b/.changes/1.21.39.json @@ -0,0 +1,22 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] X2idn and X2iedn instances are powered by 3rd generation Intel Xeon Scalable processors with an all-core turbo frequency up to 3.5 GHzAmazon EC2. C6a instances are powered by 3rd generation AMD EPYC processors.", + "type": "api-change" + }, + { + "category": "``devops-guru``", + "description": "[``botocore``] This release adds new APIs DeleteInsight to deletes the insight along with the associated anomalies, events and recommendations.", + "type": "api-change" + }, + { + "category": "``efs``", + "description": "[``botocore``] Update efs client to latest version", + "type": "api-change" + }, + { + "category": "``iottwinmaker``", + "description": "[``botocore``] This release adds the following new features: 1) ListEntities API now supports search using ExternalId. 2) BatchPutPropertyValue and GetPropertyValueHistory API now allows users to represent time in sub-second level precisions.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.4.json b/.changes/1.21.4.json new file mode 100644 index 0000000..126cc17 --- /dev/null +++ b/.changes/1.21.4.json @@ -0,0 +1,17 @@ +[ + { + "category": "``imagebuilder``", + "description": "[``botocore``] This release adds support to enable faster launching for Windows AMIs created by EC2 Image Builder.", + "type": "api-change" + }, + { + "category": "``customer-profiles``", + "description": "[``botocore``] This release introduces apis CreateIntegrationWorkflow, DeleteWorkflow, ListWorkflows, GetWorkflow and GetWorkflowSteps. These apis are used to manage and view integration workflows.", + "type": "api-change" + }, + { + "category": "``dynamodb``", + "description": "[``botocore``] DynamoDB ExecuteStatement API now supports Limit as a request parameter to specify the maximum number of items to evaluate. If specified, the service will process up to the Limit and the results will include a LastEvaluatedKey value to continue the read in a subsequent operation.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.40.json b/.changes/1.21.40.json new file mode 100644 index 0000000..f7f0a95 --- /dev/null +++ b/.changes/1.21.40.json @@ -0,0 +1,12 @@ +[ + { + "category": "``cloudwatch``", + "description": "[``botocore``] Update cloudwatch client to latest version", + "type": "api-change" + }, + { + "category": "``fsx``", + "description": "[``botocore``] This release adds support for deploying FSx for ONTAP file systems in a single Availability Zone.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.41.json b/.changes/1.21.41.json new file mode 100644 index 0000000..bdad529 --- /dev/null +++ b/.changes/1.21.41.json @@ -0,0 +1,32 @@ +[ + { + "category": "``batch``", + "description": "[``botocore``] Enables configuration updates for compute environments with BEST_FIT_PROGRESSIVE and SPOT_CAPACITY_OPTIMIZED allocation strategies.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Documentation updates for Amazon EC2.", + "type": "api-change" + }, + { + "category": "``cloudwatch``", + "description": "[``botocore``] Update cloudwatch client to latest version", + "type": "api-change" + }, + { + "category": "``appstream``", + "description": "[``botocore``] Includes updates for create and update fleet APIs to manage the session scripts locations for Elastic fleets.", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] Auto Scaling for Glue version 3.0 and later jobs to dynamically scale compute resources. This SDK change provides customers with the auto-scaled DPU usage", + "type": "api-change" + }, + { + "category": "``appflow``", + "description": "[``botocore``] Enables users to pass custom token URL parameters for Oauth2 authentication during create connector profile", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.42.json b/.changes/1.21.42.json new file mode 100644 index 0000000..a7a3827 --- /dev/null +++ b/.changes/1.21.42.json @@ -0,0 +1,17 @@ +[ + { + "category": "``lightsail``", + "description": "[``botocore``] This release adds support to describe the synchronization status of the account-level block public access feature for your Amazon Lightsail buckets.", + "type": "api-change" + }, + { + "category": "``rds``", + "description": "[``botocore``] Removes Amazon RDS on VMware with the deletion of APIs related to Custom Availability Zones and Media installation", + "type": "api-change" + }, + { + "category": "``athena``", + "description": "[``botocore``] This release adds subfields, ErrorMessage, Retryable, to the AthenaError response object in the GetQueryExecution API when a query fails.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.43.json b/.changes/1.21.43.json new file mode 100644 index 0000000..189c7ad --- /dev/null +++ b/.changes/1.21.43.json @@ -0,0 +1,52 @@ +[ + { + "category": "``textract``", + "description": "[``botocore``] This release adds support for specifying and extracting information from documents using the Queries feature within Analyze Document API", + "type": "api-change" + }, + { + "category": "``worklink``", + "description": "[``botocore``] Amazon WorkLink is no longer supported. This will be removed in a future version of the SDK.", + "type": "api-change" + }, + { + "category": "``ssm``", + "description": "[``botocore``] Added offset support for specifying the number of days to wait after the date and time specified by a CRON expression when creating SSM association.", + "type": "api-change" + }, + { + "category": "``autoscaling``", + "description": "[``botocore``] EC2 Auto Scaling now adds default instance warm-up times for all scaling activities, health check replacements, and other replacement events in the Auto Scaling instance lifecycle.", + "type": "api-change" + }, + { + "category": "``personalize``", + "description": "[``botocore``] Adding StartRecommender and StopRecommender APIs for Personalize.", + "type": "api-change" + }, + { + "category": "``kendra``", + "description": "[``botocore``] Amazon Kendra now provides a data source connector for Quip. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-quip.html", + "type": "api-change" + }, + { + "category": "``polly``", + "description": "[``botocore``] Amazon Polly adds new Austrian German voice - Hannah. Hannah is available as Neural voice only.", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] This release contains corrected HomeDirectoryMappings examples for several API functions: CreateAccess, UpdateAccess, CreateUser, and UpdateUser,.", + "type": "api-change" + }, + { + "category": "``kms``", + "description": "[``botocore``] Adds support for KMS keys and APIs that generate and verify HMAC codes", + "type": "api-change" + }, + { + "category": "``redshift``", + "description": "[``botocore``] Introduces new fields for LogDestinationType and LogExports on EnableLogging requests and Enable/Disable/DescribeLogging responses. Customers can now select CloudWatch Logs as a destination for their Audit Logs.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.44.json b/.changes/1.21.44.json new file mode 100644 index 0000000..f9d653b --- /dev/null +++ b/.changes/1.21.44.json @@ -0,0 +1,22 @@ +[ + { + "category": "``macie2``", + "description": "[``botocore``] Sensitive data findings in Amazon Macie now indicate how Macie found the sensitive data that produced a finding (originType).", + "type": "api-change" + }, + { + "category": "``rds``", + "description": "[``botocore``] Added a new cluster-level attribute to set the capacity range for Aurora Serverless v2 instances.", + "type": "api-change" + }, + { + "category": "``mgn``", + "description": "[``botocore``] Removed required annotation from input fields in Describe operations requests. Added quotaValue to ServiceQuotaExceededException", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release adds APIs to search, claim, release, list, update, and describe phone numbers. You can also use them to associate and disassociate contact flows to phone numbers.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.45.json b/.changes/1.21.45.json new file mode 100644 index 0000000..f706b98 --- /dev/null +++ b/.changes/1.21.45.json @@ -0,0 +1,47 @@ +[ + { + "category": "``wisdom``", + "description": "[``botocore``] This release updates the GetRecommendations API to include a trigger event list for classifying and grouping recommendations.", + "type": "api-change" + }, + { + "category": "``elasticache``", + "description": "[``botocore``] Doc only update for ElastiCache", + "type": "api-change" + }, + { + "category": "``iottwinmaker``", + "description": "[``botocore``] General availability (GA) for AWS IoT TwinMaker. For more information, see https://docs.aws.amazon.com/iot-twinmaker/latest/apireference/Welcome.html", + "type": "api-change" + }, + { + "category": "``secretsmanager``", + "description": "[``botocore``] Documentation updates for Secrets Manager", + "type": "api-change" + }, + { + "category": "``mediatailor``", + "description": "[``botocore``] This release introduces tiered channels and adds support for live sources. Customers using a STANDARD channel can now create programs using live sources.", + "type": "api-change" + }, + { + "category": "``storagegateway``", + "description": "[``botocore``] This release adds support for minimum of 5 character length virtual tape barcodes.", + "type": "api-change" + }, + { + "category": "``lookoutmetrics``", + "description": "[``botocore``] Added DetectMetricSetConfig API for detecting configuration required for creating metric set from provided S3 data source.", + "type": "api-change" + }, + { + "category": "``iotsitewise``", + "description": "[``botocore``] This release adds 3 new batch data query APIs : BatchGetAssetPropertyValue, BatchGetAssetPropertyValueHistory and BatchGetAssetPropertyAggregates", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] This release adds APIs to create, read, delete, list, and batch read of Glue custom entity types", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.46.json b/.changes/1.21.46.json new file mode 100644 index 0000000..d719ad9 --- /dev/null +++ b/.changes/1.21.46.json @@ -0,0 +1,12 @@ +[ + { + "category": "``chime-sdk-meetings``", + "description": "[``botocore``] Include additional exceptions types.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Adds support for waiters that automatically poll for a deleted NAT Gateway until it reaches the deleted state.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.5.json b/.changes/1.21.5.json new file mode 100644 index 0000000..fee48a8 --- /dev/null +++ b/.changes/1.21.5.json @@ -0,0 +1,17 @@ +[ + { + "category": "``translate``", + "description": "[``botocore``] This release enables customers to use translation settings for formality customization in their synchronous translation output.", + "type": "api-change" + }, + { + "category": "``wafv2``", + "description": "[``botocore``] Updated descriptions for logging configuration.", + "type": "api-change" + }, + { + "category": "``apprunner``", + "description": "[``botocore``] AWS App Runner adds a Java platform (Corretto 8, Corretto 11 runtimes) and a Node.js 14 runtime.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.6.json b/.changes/1.21.6.json new file mode 100644 index 0000000..a6d42fb --- /dev/null +++ b/.changes/1.21.6.json @@ -0,0 +1,17 @@ +[ + { + "category": "``transfer``", + "description": "[``botocore``] The file input selection feature provides the ability to use either the originally uploaded file or the output file from the previous workflow step, enabling customers to make multiple copies of the original file while keeping the source file intact for file archival.", + "type": "api-change" + }, + { + "category": "``lambda``", + "description": "[``botocore``] Lambda releases .NET 6 managed runtime to be available in all commercial regions.", + "type": "api-change" + }, + { + "category": "``textract``", + "description": "[``botocore``] Added support for merged cells and column header for table response.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.7.json b/.changes/1.21.7.json new file mode 100644 index 0000000..3ae5e9f --- /dev/null +++ b/.changes/1.21.7.json @@ -0,0 +1,42 @@ +[ + { + "category": "``route53``", + "description": "[``botocore``] SDK doc update for Route 53 to update some parameters with new information.", + "type": "api-change" + }, + { + "category": "``databrew``", + "description": "[``botocore``] This AWS Glue Databrew release adds feature to merge job outputs into a max number of files for S3 File output type.", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] Support automatic pagination when listing AWS Transfer Family resources.", + "type": "api-change" + }, + { + "category": "``s3control``", + "description": "[``botocore``] Amazon S3 Batch Operations adds support for new integrity checking capabilities in Amazon S3.", + "type": "api-change" + }, + { + "category": "``s3``", + "description": "[``botocore``] This release adds support for new integrity checking capabilities in Amazon S3. You can choose from four supported checksum algorithms for data integrity checking on your upload and download requests. In addition, AWS SDK can automatically calculate a checksum as it streams data into S3", + "type": "api-change" + }, + { + "category": "``fms``", + "description": "[``botocore``] AWS Firewall Manager now supports the configuration of AWS Network Firewall policies with either centralized or distributed deployment models. This release also adds support for custom endpoint configuration, where you can choose which Availability Zones to create firewall endpoints in.", + "type": "api-change" + }, + { + "category": "``lightsail``", + "description": "[``botocore``] This release adds support to delete and create Lightsail default key pairs that you can use with Lightsail instances.", + "type": "api-change" + }, + { + "category": "``autoscaling``", + "description": "[``botocore``] You can now hibernate instances in a warm pool to stop instances without deleting their RAM contents. You can now also return instances to the warm pool on scale in, instead of always terminating capacity that you will need later.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.8.json b/.changes/1.21.8.json new file mode 100644 index 0000000..aa6ba4b --- /dev/null +++ b/.changes/1.21.8.json @@ -0,0 +1,12 @@ +[ + { + "category": "``elasticache``", + "description": "[``botocore``] Doc only update for ElastiCache", + "type": "api-change" + }, + { + "category": "``panorama``", + "description": "[``botocore``] Added NTP server configuration parameter to ProvisionDevice operation. Added alternate software fields to DescribeDevice response", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.21.9.json b/.changes/1.21.9.json new file mode 100644 index 0000000..ea84430 --- /dev/null +++ b/.changes/1.21.9.json @@ -0,0 +1,47 @@ +[ + { + "category": "``finspace-data``", + "description": "[``botocore``] Add new APIs for managing Users and Permission Groups.", + "type": "api-change" + }, + { + "category": "``amplify``", + "description": "[``botocore``] Add repositoryCloneMethod field for hosting an Amplify app. This field shows what authorization method is used to clone the repo: SSH, TOKEN, or SIGV4.", + "type": "api-change" + }, + { + "category": "``fsx``", + "description": "[``botocore``] This release adds support for the following FSx for OpenZFS features: snapshot lifecycle transition messages, force flag for deleting file systems with child resources, LZ4 data compression, custom record sizes, and unsetting volume quotas and reservations.", + "type": "api-change" + }, + { + "category": "``fis``", + "description": "[``botocore``] This release adds logging support for AWS Fault Injection Simulator experiments. Experiment templates can now be configured to send experiment activity logs to Amazon CloudWatch Logs or to an S3 bucket.", + "type": "api-change" + }, + { + "category": "``route53-recovery-cluster``", + "description": "[``botocore``] This release adds a new API option to enable overriding safety rules to allow routing control state updates.", + "type": "api-change" + }, + { + "category": "``amplifyuibuilder``", + "description": "[``botocore``] We are adding the ability to configure workflows and actions for components.", + "type": "api-change" + }, + { + "category": "``athena``", + "description": "[``botocore``] This release adds support for updating an existing named query.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] This release adds support for new AMI property 'lastLaunchedTime'", + "type": "api-change" + }, + { + "category": "``servicecatalog-appregistry``", + "description": "[``botocore``] AppRegistry is deprecating Application and Attribute-Group Name update feature. In this release, we are marking the name attributes for Update APIs as deprecated to give a heads up to our customers.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.0.json b/.changes/1.22.0.json new file mode 100644 index 0000000..2b12e65 --- /dev/null +++ b/.changes/1.22.0.json @@ -0,0 +1,32 @@ +[ + { + "category": "``gamelift``", + "description": "[``botocore``] Documentation updates for Amazon GameLift.", + "type": "api-change" + }, + { + "category": "``mq``", + "description": "[``botocore``] This release adds the CRITICAL_ACTION_REQUIRED broker state and the ActionRequired API property. CRITICAL_ACTION_REQUIRED informs you when your broker is degraded. ActionRequired provides you with a code which you can use to find instructions in the Developer Guide on how to resolve the issue.", + "type": "api-change" + }, + { + "category": "IMDS", + "description": "[``botocore``] Added resiliency mechanisms to IMDS Credential Fetcher", + "type": "feature" + }, + { + "category": "``securityhub``", + "description": "[``botocore``] Security Hub now lets you opt-out of auto-enabling the defaults standards (CIS and FSBP) in accounts that are auto-enabled with Security Hub via Security Hub's integration with AWS Organizations.", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release adds SearchUsers API which can be used to search for users with a Connect Instance", + "type": "api-change" + }, + { + "category": "``rds-data``", + "description": "[``botocore``] Support to receive SQL query results in the form of a simplified JSON string. This enables developers using the new JSON string format to more easily convert it to an object using popular JSON string parsing libraries.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.1.json b/.changes/1.22.1.json new file mode 100644 index 0000000..c7aa2bc --- /dev/null +++ b/.changes/1.22.1.json @@ -0,0 +1,37 @@ +[ + { + "category": "``lightsail``", + "description": "[``botocore``] This release adds support for Lightsail load balancer HTTP to HTTPS redirect and TLS policy configuration.", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] SageMaker Inference Recommender now accepts customer KMS key ID for encryption of endpoints and compilation outputs created during inference recommendation.", + "type": "api-change" + }, + { + "category": "``pricing``", + "description": "[``botocore``] Documentation updates for Price List API", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] This release adds documentation for the APIs to create, read, delete, list, and batch read of AWS Glue custom patterns, and for Lake Formation configuration settings in the AWS Glue crawler.", + "type": "api-change" + }, + { + "category": "``cloudfront``", + "description": "[``botocore``] CloudFront now supports the Server-Timing header in HTTP responses sent from CloudFront. You can use this header to view metrics that help you gain insights about the behavior and performance of CloudFront. To use this header, enable it in a response headers policy.", + "type": "api-change" + }, + { + "category": "``ivschat``", + "description": "[``botocore``] Adds new APIs for IVS Chat, a feature for building interactive chat experiences alongside an IVS broadcast.", + "type": "api-change" + }, + { + "category": "``network-firewall``", + "description": "[``botocore``] AWS Network Firewall now enables customers to use a customer managed AWS KMS key for the encryption of their firewall resources.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.10.json b/.changes/1.22.10.json new file mode 100644 index 0000000..c91efe3 --- /dev/null +++ b/.changes/1.22.10.json @@ -0,0 +1,17 @@ +[ + { + "category": "``evidently``", + "description": "[``botocore``] Add detail message inside GetExperimentResults API response to indicate experiment result availability", + "type": "api-change" + }, + { + "category": "``ssm-contacts``", + "description": "[``botocore``] Fixed an error in the DescribeEngagement example for AWS Incident Manager.", + "type": "api-change" + }, + { + "category": "``cloudcontrol``", + "description": "[``botocore``] SDK release for Cloud Control API to include paginators for Python SDK.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.11.json b/.changes/1.22.11.json new file mode 100644 index 0000000..19f7745 --- /dev/null +++ b/.changes/1.22.11.json @@ -0,0 +1,27 @@ +[ + { + "category": "``migration-hub-refactor-spaces``", + "description": "[``botocore``] AWS Migration Hub Refactor Spaces documentation only update to fix a formatting issue.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Added support for using NitroTPM and UEFI Secure Boot on EC2 instances.", + "type": "api-change" + }, + { + "category": "``emr``", + "description": "[``botocore``] Update emr client to latest version", + "type": "api-change" + }, + { + "category": "``compute-optimizer``", + "description": "[``botocore``] Documentation updates for Compute Optimizer", + "type": "api-change" + }, + { + "category": "``eks``", + "description": "[``botocore``] Adds BOTTLEROCKET_ARM_64_NVIDIA and BOTTLEROCKET_x86_64_NVIDIA AMI types to EKS managed nodegroups", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.12.json b/.changes/1.22.12.json new file mode 100644 index 0000000..f811339 --- /dev/null +++ b/.changes/1.22.12.json @@ -0,0 +1,12 @@ +[ + { + "category": "``secretsmanager``", + "description": "[``botocore``] Doc only update for Secrets Manager that fixes several customer-reported issues.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] This release updates AWS PrivateLink APIs to support IPv6 for PrivateLink Services and Endpoints of type 'Interface'.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.13.json b/.changes/1.22.13.json new file mode 100644 index 0000000..077414d --- /dev/null +++ b/.changes/1.22.13.json @@ -0,0 +1,57 @@ +[ + { + "category": "``ivschat``", + "description": "[``botocore``] Documentation-only updates for IVS Chat API Reference.", + "type": "api-change" + }, + { + "category": "``lambda``", + "description": "[``botocore``] Lambda releases NodeJs 16 managed runtime to be available in all commercial regions.", + "type": "api-change" + }, + { + "category": "``kendra``", + "description": "[``botocore``] Amazon Kendra now provides a data source connector for Jira. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-jira.html", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] AWS Transfer Family now accepts ECDSA keys for server host keys", + "type": "api-change" + }, + { + "category": "``iot``", + "description": "[``botocore``] Documentation update for China region ListMetricValues for IoT", + "type": "api-change" + }, + { + "category": "``workspaces``", + "description": "[``botocore``] Increased the character limit of the login message from 600 to 850 characters.", + "type": "api-change" + }, + { + "category": "``finspace-data``", + "description": "[``botocore``] We've now deprecated CreateSnapshot permission for creating a data view, instead use CreateDataView permission.", + "type": "api-change" + }, + { + "category": "``lightsail``", + "description": "[``botocore``] This release adds support to include inactive database bundles in the response of the GetRelationalDatabaseBundles request.", + "type": "api-change" + }, + { + "category": "``outposts``", + "description": "[``botocore``] Documentation updates for AWS Outposts.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] This release introduces a target type Gateway Load Balancer Endpoint for mirrored traffic. Customers can now specify GatewayLoadBalancerEndpoint option during the creation of a traffic mirror target.", + "type": "api-change" + }, + { + "category": "``ssm-incidents``", + "description": "[``botocore``] Adding support for dynamic SSM Runbook parameter values. Updating validation pattern for engagements. Adding ConflictException to UpdateReplicationSet API contract.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.2.json b/.changes/1.22.2.json new file mode 100644 index 0000000..126260d --- /dev/null +++ b/.changes/1.22.2.json @@ -0,0 +1,37 @@ +[ + { + "category": "``rekognition``", + "description": "[``botocore``] This release adds support to configure stream-processor resources for label detections on streaming-videos. UpateStreamProcessor API is also launched with this release, which could be used to update an existing stream-processor.", + "type": "api-change" + }, + { + "category": "``cloudtrail``", + "description": "[``botocore``] Increases the retention period maximum to 2557 days. Deprecates unused fields of the ListEventDataStores API response. Updates documentation.", + "type": "api-change" + }, + { + "category": "``lookoutequipment``", + "description": "[``botocore``] This release adds the following new features: 1) Introduces an option for automatic schema creation 2) Now allows for Ingestion of data containing most common errors and allows automatic data cleaning 3) Introduces new API ListSensorStatistics that gives further information about the ingested data", + "type": "api-change" + }, + { + "category": "``iotwireless``", + "description": "[``botocore``] Add list support for event configurations, allow to get and update event configurations by resource type, support LoRaWAN events; Make NetworkAnalyzerConfiguration as a resource, add List, Create, Delete API support; Add FCntStart attribute support for ABP WirelessDevice.", + "type": "api-change" + }, + { + "category": "``amplify``", + "description": "[``botocore``] Documentation only update to support the Amplify GitHub App feature launch", + "type": "api-change" + }, + { + "category": "``chime-sdk-media-pipelines``", + "description": "[``botocore``] For Amazon Chime SDK meetings, the Amazon Chime Media Pipelines SDK allows builders to capture audio, video, and content share streams. You can also capture meeting events, live transcripts, and data messages. The pipelines save the artifacts to an Amazon S3 bucket that you designate.", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] Amazon SageMaker Autopilot adds support for custom validation dataset and validation ratio through the CreateAutoMLJob and DescribeAutoMLJob APIs.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.3.json b/.changes/1.22.3.json new file mode 100644 index 0000000..f5c401c --- /dev/null +++ b/.changes/1.22.3.json @@ -0,0 +1,32 @@ +[ + { + "category": "``auditmanager``", + "description": "[``botocore``] This release adds documentation updates for Audit Manager. We provided examples of how to use the Custom_ prefix for the keywordValue attribute. We also provided more details about the DeleteAssessmentReport operation.", + "type": "api-change" + }, + { + "category": "``network-firewall``", + "description": "[``botocore``] AWS Network Firewall adds support for stateful threat signature AWS managed rule groups.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] This release adds support to query the public key and creation date of EC2 Key Pairs. Additionally, the format (pem or ppk) of a key pair can be specified when creating a new key pair.", + "type": "api-change" + }, + { + "category": "``braket``", + "description": "[``botocore``] This release enables Braket Hybrid Jobs with Embedded Simulators to have multiple instances.", + "type": "api-change" + }, + { + "category": "``guardduty``", + "description": "[``botocore``] Documentation update for API description.", + "type": "api-change" + }, + { + "category": "``connect``", + "description": "[``botocore``] This release introduces an API for changing the current agent status of a user in Connect.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.4.json b/.changes/1.22.4.json new file mode 100644 index 0000000..7f23010 --- /dev/null +++ b/.changes/1.22.4.json @@ -0,0 +1,27 @@ +[ + { + "category": "``rds``", + "description": "[``botocore``] Feature - Adds support for Internet Protocol Version 6 (IPv6) on RDS database instances.", + "type": "api-change" + }, + { + "category": "``codeguru-reviewer``", + "description": "[``botocore``] Amazon CodeGuru Reviewer now supports suppressing recommendations from being generated on specific files and directories.", + "type": "api-change" + }, + { + "category": "``ssm``", + "description": "[``botocore``] Update the StartChangeRequestExecution, adding TargetMaps to the Runbook parameter", + "type": "api-change" + }, + { + "category": "``mediaconvert``", + "description": "[``botocore``] AWS Elemental MediaConvert SDK nows supports creation of Dolby Vision profile 8.1, the ability to generate black frames of video, and introduces audio-only DASH and CMAF support.", + "type": "api-change" + }, + { + "category": "``wafv2``", + "description": "[``botocore``] You can now inspect all request headers and all cookies. You can now specify how to handle oversize body contents in your rules that inspect the body.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.5.json b/.changes/1.22.5.json new file mode 100644 index 0000000..cdd1432 --- /dev/null +++ b/.changes/1.22.5.json @@ -0,0 +1,17 @@ +[ + { + "category": "``organizations``", + "description": "[``botocore``] This release adds the INVALID_PAYMENT_INSTRUMENT as a fail reason and an error message.", + "type": "api-change" + }, + { + "category": "``synthetics``", + "description": "[``botocore``] CloudWatch Synthetics has introduced a new feature to provide customers with an option to delete the underlying resources that Synthetics canary creates when the user chooses to delete the canary.", + "type": "api-change" + }, + { + "category": "``outposts``", + "description": "[``botocore``] This release adds a new API called ListAssets to the Outposts SDK, which lists the hardware assets in an Outpost.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.6.json b/.changes/1.22.6.json new file mode 100644 index 0000000..e8fc848 --- /dev/null +++ b/.changes/1.22.6.json @@ -0,0 +1,27 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] Adds support for allocating Dedicated Hosts on AWS Outposts. The AllocateHosts API now accepts an OutpostArn request parameter, and the DescribeHosts API now includes an OutpostArn response parameter.", + "type": "api-change" + }, + { + "category": "``s3``", + "description": "[``botocore``] Documentation only update for doc bug fixes for the S3 API docs.", + "type": "api-change" + }, + { + "category": "``kinesisvideo``", + "description": "[``botocore``] Add support for multiple image feature related APIs for configuring image generation and notification of a video stream. Add \"GET_IMAGES\" to the list of supported API names for the GetDataEndpoint API.", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] SageMaker Autopilot adds new metrics for all candidate models generated by Autopilot experiments; RStudio on SageMaker now allows users to bring your own development environment in a custom image.", + "type": "api-change" + }, + { + "category": "``kinesis-video-archived-media``", + "description": "[``botocore``] Add support for GetImages API for retrieving images from a video stream", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.7.json b/.changes/1.22.7.json new file mode 100644 index 0000000..aade32b --- /dev/null +++ b/.changes/1.22.7.json @@ -0,0 +1,22 @@ +[ + { + "category": "``ssm``", + "description": "[``botocore``] This release adds the TargetMaps parameter in SSM State Manager API.", + "type": "api-change" + }, + { + "category": "``backup``", + "description": "[``botocore``] Adds support to 2 new filters about job complete time for 3 list jobs APIs in AWS Backup", + "type": "api-change" + }, + { + "category": "``lightsail``", + "description": "[``botocore``] Documentation updates for Lightsail", + "type": "api-change" + }, + { + "category": "``iotsecuretunneling``", + "description": "[``botocore``] This release introduces a new API RotateTunnelAccessToken that allow revoking the existing tokens and generate new tokens", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.8.json b/.changes/1.22.8.json new file mode 100644 index 0000000..16820c7 --- /dev/null +++ b/.changes/1.22.8.json @@ -0,0 +1,22 @@ +[ + { + "category": "``ec2``", + "description": "[``botocore``] Amazon EC2 I4i instances are powered by 3rd generation Intel Xeon Scalable processors and feature up to 30 TB of local AWS Nitro SSD storage", + "type": "api-change" + }, + { + "category": "``kendra``", + "description": "[``botocore``] AWS Kendra now supports hierarchical facets for a query. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/filtering.html", + "type": "api-change" + }, + { + "category": "``iot``", + "description": "[``botocore``] AWS IoT Jobs now allows you to create up to 100,000 active continuous and snapshot jobs by using concurrency control.", + "type": "api-change" + }, + { + "category": "``datasync``", + "description": "[``botocore``] AWS DataSync now supports a new ObjectTags Task API option that can be used to control whether Object Tags are transferred.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.22.9.json b/.changes/1.22.9.json new file mode 100644 index 0000000..8657227 --- /dev/null +++ b/.changes/1.22.9.json @@ -0,0 +1,32 @@ +[ + { + "category": "``rds``", + "description": "[``botocore``] Various documentation improvements.", + "type": "api-change" + }, + { + "category": "``redshift``", + "description": "[``botocore``] Introduces new field 'LoadSampleData' in CreateCluster operation. Customers can now specify 'LoadSampleData' option during creation of a cluster, which results in loading of sample data in the cluster that is created.", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Add new state values for IPAMs, IPAM Scopes, and IPAM Pools.", + "type": "api-change" + }, + { + "category": "``mediapackage``", + "description": "[``botocore``] This release adds Dvb Dash 2014 as an available profile option for Dash Origin Endpoints.", + "type": "api-change" + }, + { + "category": "``securityhub``", + "description": "[``botocore``] Documentation updates for Security Hub API reference", + "type": "api-change" + }, + { + "category": "``location``", + "description": "[``botocore``] Amazon Location Service now includes a MaxResults parameter for ListGeofences requests.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.0.json b/.changes/1.23.0.json new file mode 100644 index 0000000..c7ba210 --- /dev/null +++ b/.changes/1.23.0.json @@ -0,0 +1,12 @@ +[ + { + "category": "Loaders", + "description": "[``botocore``] Support for loading gzip compressed model files.", + "type": "feature" + }, + { + "category": "``grafana``", + "description": "[``botocore``] This release adds APIs for creating and deleting API keys in an Amazon Managed Grafana workspace.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.1.json b/.changes/1.23.1.json new file mode 100644 index 0000000..1f4945b --- /dev/null +++ b/.changes/1.23.1.json @@ -0,0 +1,37 @@ +[ + { + "category": "``resiliencehub``", + "description": "[``botocore``] In this release, we are introducing support for Amazon Elastic Container Service, Amazon Route 53, AWS Elastic Disaster Recovery, AWS Backup in addition to the existing supported Services. This release also supports Terraform file input from S3 and scheduling daily assessments", + "type": "api-change" + }, + { + "category": "``servicecatalog``", + "description": "[``botocore``] Updated the descriptions for the ListAcceptedPortfolioShares API description and the PortfolioShareType parameters.", + "type": "api-change" + }, + { + "category": "``discovery``", + "description": "[``botocore``] Add Migration Evaluator Collector details to the GetDiscoverySummary API response", + "type": "api-change" + }, + { + "category": "``sts``", + "description": "[``botocore``] Documentation updates for AWS Security Token Service.", + "type": "api-change" + }, + { + "category": "``workspaces-web``", + "description": "[``botocore``] Amazon WorkSpaces Web now supports Administrator timeout control", + "type": "api-change" + }, + { + "category": "``rekognition``", + "description": "[``botocore``] Documentation updates for Amazon Rekognition.", + "type": "api-change" + }, + { + "category": "``cloudfront``", + "description": "[``botocore``] Introduced a new error (TooLongCSPInResponseHeadersPolicy) that is returned when the value of the Content-Security-Policy header in a response headers policy exceeds the maximum allowed length.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.2.json b/.changes/1.23.2.json new file mode 100644 index 0000000..83f82a5 --- /dev/null +++ b/.changes/1.23.2.json @@ -0,0 +1,12 @@ +[ + { + "category": "``kms``", + "description": "[``botocore``] Add HMAC best practice tip, annual rotation of AWS managed keys.", + "type": "api-change" + }, + { + "category": "``glue``", + "description": "[``botocore``] This release adds a new optional parameter called codeGenNodeConfiguration to CRUD job APIs that allows users to manage visual jobs via APIs. The updated CreateJob and UpdateJob will create jobs that can be viewed in Glue Studio as a visual graph. GetJob can be used to get codeGenNodeConfiguration.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.3.json b/.changes/1.23.3.json new file mode 100644 index 0000000..1110aaa --- /dev/null +++ b/.changes/1.23.3.json @@ -0,0 +1,32 @@ +[ + { + "category": "``greengrassv2``", + "description": "[``botocore``] This release adds the new DeleteDeployment API operation that you can use to delete deployment resources. This release also adds support for discontinued AWS-provided components, so AWS can communicate when a component has any issues that you should consider before you deploy it.", + "type": "api-change" + }, + { + "category": "``quicksight``", + "description": "[``botocore``] API UpdatePublicSharingSettings enables IAM admins to enable/disable account level setting for public access of dashboards. When enabled, owners/co-owners for dashboards can enable public access on their dashboards. These dashboards can only be accessed through share link or embedding.", + "type": "api-change" + }, + { + "category": "``appmesh``", + "description": "[``botocore``] This release updates the existing Create and Update APIs for meshes and virtual nodes by adding a new IP preference field. This new IP preference field can be used to control the IP versions being used with the mesh and allows for IPv6 support within App Mesh.", + "type": "api-change" + }, + { + "category": "``batch``", + "description": "[``botocore``] Documentation updates for AWS Batch.", + "type": "api-change" + }, + { + "category": "``iotevents-data``", + "description": "[``botocore``] Introducing new API for deleting detectors: BatchDeleteDetector.", + "type": "api-change" + }, + { + "category": "``transfer``", + "description": "[``botocore``] AWS Transfer Family now supports SetStat server configuration option, which provides the ability to ignore SetStat command issued by file transfer clients, enabling customers to upload files without any errors.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.4.json b/.changes/1.23.4.json new file mode 100644 index 0000000..e88a0ec --- /dev/null +++ b/.changes/1.23.4.json @@ -0,0 +1,17 @@ +[ + { + "category": "``gamesparks``", + "description": "[``botocore``] This release adds an optional DeploymentResult field in the responses of GetStageDeploymentIntegrationTests and ListStageDeploymentIntegrationTests APIs.", + "type": "api-change" + }, + { + "category": "StreamingBody", + "description": "[``botocore``] Allow StreamingBody to be used as a context manager", + "type": "enhancement" + }, + { + "category": "``lookoutmetrics``", + "description": "[``botocore``] In this release we added SnsFormat to SNSConfiguration to support human readable alert.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.5.json b/.changes/1.23.5.json new file mode 100644 index 0000000..e055b57 --- /dev/null +++ b/.changes/1.23.5.json @@ -0,0 +1,12 @@ +[ + { + "category": "``comprehend``", + "description": "[``botocore``] Comprehend releases 14 new entity types for DetectPiiEntities and ContainsPiiEntities APIs.", + "type": "api-change" + }, + { + "category": "``logs``", + "description": "[``botocore``] Doc-only update to publish the new valid values for log retention", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.6.json b/.changes/1.23.6.json new file mode 100644 index 0000000..f104a45 --- /dev/null +++ b/.changes/1.23.6.json @@ -0,0 +1,17 @@ +[ + { + "category": "``elasticache``", + "description": "[``botocore``] Added support for encryption in transit for Memcached clusters. Customers can now launch Memcached cluster with encryption in transit enabled when using Memcached version 1.6.12 or later.", + "type": "api-change" + }, + { + "category": "``forecast``", + "description": "[``botocore``] New APIs for Monitor that help you understand how your predictors perform over time.", + "type": "api-change" + }, + { + "category": "``personalize``", + "description": "[``botocore``] Adding modelMetrics as part of DescribeRecommender API response for Personalize.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.7.json b/.changes/1.23.7.json new file mode 100644 index 0000000..85b470d --- /dev/null +++ b/.changes/1.23.7.json @@ -0,0 +1,27 @@ +[ + { + "category": "``mediaconvert``", + "description": "[``botocore``] AWS Elemental MediaConvert SDK has added support for rules that constrain Automatic-ABR rendition selection when generating ABR package ladders.", + "type": "api-change" + }, + { + "category": "``cognito-idp``", + "description": "[``botocore``] Amazon Cognito now supports requiring attribute verification (ex. email and phone number) before update.", + "type": "api-change" + }, + { + "category": "``networkmanager``", + "description": "[``botocore``] This release adds Multi Account API support for a TGW Global Network, to enable and disable AWSServiceAccess with AwsOrganizations for Network Manager service and dependency CloudFormation StackSets service.", + "type": "api-change" + }, + { + "category": "``ivschat``", + "description": "[``botocore``] Doc-only update. For MessageReviewHandler structure, added timeout period in the description of the fallbackResult field", + "type": "api-change" + }, + { + "category": "``ec2``", + "description": "[``botocore``] Stop Protection feature enables customers to protect their instances from accidental stop actions.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.changes/1.23.8.json b/.changes/1.23.8.json new file mode 100644 index 0000000..6018adc --- /dev/null +++ b/.changes/1.23.8.json @@ -0,0 +1,42 @@ +[ + { + "category": "``secretsmanager``", + "description": "[``botocore``] Documentation updates for Secrets Manager", + "type": "api-change" + }, + { + "category": "``fsx``", + "description": "[``botocore``] This release adds root squash support to FSx for Lustre to restrict root level access from clients by mapping root users to a less-privileged user/group with limited permissions.", + "type": "api-change" + }, + { + "category": "``lookoutmetrics``", + "description": "[``botocore``] Adding AthenaSourceConfig for MetricSet APIs to support Athena as a data source.", + "type": "api-change" + }, + { + "category": "``voice-id``", + "description": "[``botocore``] VoiceID will now automatically expire Speakers if they haven't been accessed for Enrollment, Re-enrollment or Successful Auth for three years. The Speaker APIs now return a \"LastAccessedAt\" time for Speakers, and the EvaluateSession API returns \"SPEAKER_EXPIRED\" Auth Decision for EXPIRED Speakers.", + "type": "api-change" + }, + { + "category": "``cloudformation``", + "description": "[``botocore``] Add a new parameter statusReason to DescribeStackSetOperation output for additional details", + "type": "api-change" + }, + { + "category": "``apigateway``", + "description": "[``botocore``] Documentation updates for Amazon API Gateway", + "type": "api-change" + }, + { + "category": "``apprunner``", + "description": "[``botocore``] Documentation-only update added for CodeConfiguration.", + "type": "api-change" + }, + { + "category": "``sagemaker``", + "description": "[``botocore``] Amazon SageMaker Autopilot adds support for manually selecting features from the input dataset using the CreateAutoMLJob API.", + "type": "api-change" + } +] \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..94d19fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,73 @@ +--- +name: "🐛 Bug Report" +description: Report a bug +title: "(short issue description)" +labels: [bug, needs-triage] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Describe the bug + description: What is the problem? A clear and concise description of the bug. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: | + What did you expect to happen? + validations: + required: true + - type: textarea + id: current + attributes: + label: Current Behavior + description: | + What actually happened? + + Please include full errors, uncaught exceptions, stack traces, and relevant logs. + If service responses are relevant, please include wire logs. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction Steps + description: | + Provide a self-contained, concise snippet of code that can be used to reproduce the issue. + For more complex issues provide a repo with the smallest sample that reproduces the bug. + + Avoid including business logic or unrelated code, it makes diagnosis more difficult. + The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Possible Solution + description: | + Suggest a fix/reason for the bug + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional Information/Context + description: | + Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. + validations: + required: false + - type: input + id: sdk-version + attributes: + label: SDK version used + validations: + required: true + - type: input + id: environment + attributes: + label: Environment details (OS name and version, etc.) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd93a08..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: needs-triage -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Steps to reproduce** -If you have a runnable example, please include it as a snippet or link to a repository/gist for larger code examples. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Debug logs** -Full stack trace by adding `boto3.set_stream_logger('')` to your code. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ba13e0..f047a74 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,6 @@ -blank_issues_enabled: false +--- +blank_issues_enabled: false +contact_links: + - name: 💬 General Question + url: https://github.com/boto/boto3/discussions/categories/q-a + about: Please ask and answer questions as a discussion thread \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..7d73869 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,23 @@ +--- +name: "📕 Documentation Issue" +description: Report an issue in the API Reference documentation or Developer Guide +title: "(short issue description)" +labels: [documentation, needs-triage] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Describe the issue + description: A clear and concise description of the issue. + validations: + required: true + + - type: textarea + id: links + attributes: + label: Links + description: | + Include links to affected documentation page(s). + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..60d2431 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,59 @@ +--- +name: 🚀 Feature Request +description: Suggest an idea for this project +title: "(short issue description)" +labels: [feature-request, needs-triage] +assignees: [] +body: + - type: textarea + id: description + attributes: + label: Describe the feature + description: A clear and concise description of the feature you are proposing. + validations: + required: true + - type: textarea + id: use-case + attributes: + label: Use Case + description: | + Why do you need this feature? For example: "I'm always frustrated when..." + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: | + Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. + validations: + required: false + - type: textarea + id: other + attributes: + label: Other Information + description: | + Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. + validations: + required: false + - type: checkboxes + id: ack + attributes: + label: Acknowledgements + options: + - label: I may be able to implement this feature request + required: false + - label: This feature might incur a breaking change + required: false + - type: input + id: sdk-version + attributes: + label: SDK version used + validations: + required: true + - type: input + id: environment + attributes: + label: Environment details (OS name and version, etc.) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 22f6bf1..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: needs-triage, feature-request -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. diff --git a/.github/ISSUE_TEMPLATE/guidance-issue.md b/.github/ISSUE_TEMPLATE/guidance-issue.md deleted file mode 100644 index 1a9b56d..0000000 --- a/.github/ISSUE_TEMPLATE/guidance-issue.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Guidance issue -about: Create a report to help us improve -title: '' -labels: needs-triage, guidance -assignees: '' - ---- - -Please fill out the sections below to help us address your issue. - -**What issue did you see ?** - -**Steps to reproduce** -If you have a runnable example, please include it as a snippet or link to a repository/gist for larger code examples. - -**Debug logs** -Full stack trace by adding `boto3.set_stream_logger('')` to your code. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 881c315..2fd3699 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,26 @@ exclude: ^(.github|.changes|docs/|boto3/compat.py|boto3/data|CHANGELOG.rst) repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + - repo: 'https://github.com/pre-commit/pre-commit-hooks' + rev: v4.2.0 hooks: - - id: check-yaml - - id: end-of-file-fixer - - id: trailing-whitespace -- repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: 'https://github.com/asottile/pyupgrade' + rev: v2.32.1 hooks: - - id: flake8 + - id: pyupgrade + args: + - '--py36-plus' + - repo: 'https://github.com/PyCQA/isort' + rev: 5.10.1 + hooks: + - id: isort + - repo: 'https://github.com/psf/black' + rev: 22.3.0 + hooks: + - id: black + - repo: 'https://github.com/pycqa/flake8' + rev: 4.0.1 + hooks: + - id: flake8 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c014a3d..2a028bb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,859 @@ CHANGELOG ========= +1.23.8 +====== + +* api-change:``secretsmanager``: [``botocore``] Documentation updates for Secrets Manager +* api-change:``fsx``: [``botocore``] This release adds root squash support to FSx for Lustre to restrict root level access from clients by mapping root users to a less-privileged user/group with limited permissions. +* api-change:``lookoutmetrics``: [``botocore``] Adding AthenaSourceConfig for MetricSet APIs to support Athena as a data source. +* api-change:``voice-id``: [``botocore``] VoiceID will now automatically expire Speakers if they haven't been accessed for Enrollment, Re-enrollment or Successful Auth for three years. The Speaker APIs now return a "LastAccessedAt" time for Speakers, and the EvaluateSession API returns "SPEAKER_EXPIRED" Auth Decision for EXPIRED Speakers. +* api-change:``cloudformation``: [``botocore``] Add a new parameter statusReason to DescribeStackSetOperation output for additional details +* api-change:``apigateway``: [``botocore``] Documentation updates for Amazon API Gateway +* api-change:``apprunner``: [``botocore``] Documentation-only update added for CodeConfiguration. +* api-change:``sagemaker``: [``botocore``] Amazon SageMaker Autopilot adds support for manually selecting features from the input dataset using the CreateAutoMLJob API. + + +1.23.7 +====== + +* api-change:``mediaconvert``: [``botocore``] AWS Elemental MediaConvert SDK has added support for rules that constrain Automatic-ABR rendition selection when generating ABR package ladders. +* api-change:``cognito-idp``: [``botocore``] Amazon Cognito now supports requiring attribute verification (ex. email and phone number) before update. +* api-change:``networkmanager``: [``botocore``] This release adds Multi Account API support for a TGW Global Network, to enable and disable AWSServiceAccess with AwsOrganizations for Network Manager service and dependency CloudFormation StackSets service. +* api-change:``ivschat``: [``botocore``] Doc-only update. For MessageReviewHandler structure, added timeout period in the description of the fallbackResult field +* api-change:``ec2``: [``botocore``] Stop Protection feature enables customers to protect their instances from accidental stop actions. + + +1.23.6 +====== + +* api-change:``elasticache``: [``botocore``] Added support for encryption in transit for Memcached clusters. Customers can now launch Memcached cluster with encryption in transit enabled when using Memcached version 1.6.12 or later. +* api-change:``forecast``: [``botocore``] New APIs for Monitor that help you understand how your predictors perform over time. +* api-change:``personalize``: [``botocore``] Adding modelMetrics as part of DescribeRecommender API response for Personalize. + + +1.23.5 +====== + +* api-change:``comprehend``: [``botocore``] Comprehend releases 14 new entity types for DetectPiiEntities and ContainsPiiEntities APIs. +* api-change:``logs``: [``botocore``] Doc-only update to publish the new valid values for log retention + + +1.23.4 +====== + +* api-change:``gamesparks``: [``botocore``] This release adds an optional DeploymentResult field in the responses of GetStageDeploymentIntegrationTests and ListStageDeploymentIntegrationTests APIs. +* enhancement:StreamingBody: [``botocore``] Allow StreamingBody to be used as a context manager +* api-change:``lookoutmetrics``: [``botocore``] In this release we added SnsFormat to SNSConfiguration to support human readable alert. + + +1.23.3 +====== + +* api-change:``greengrassv2``: [``botocore``] This release adds the new DeleteDeployment API operation that you can use to delete deployment resources. This release also adds support for discontinued AWS-provided components, so AWS can communicate when a component has any issues that you should consider before you deploy it. +* api-change:``quicksight``: [``botocore``] API UpdatePublicSharingSettings enables IAM admins to enable/disable account level setting for public access of dashboards. When enabled, owners/co-owners for dashboards can enable public access on their dashboards. These dashboards can only be accessed through share link or embedding. +* api-change:``appmesh``: [``botocore``] This release updates the existing Create and Update APIs for meshes and virtual nodes by adding a new IP preference field. This new IP preference field can be used to control the IP versions being used with the mesh and allows for IPv6 support within App Mesh. +* api-change:``batch``: [``botocore``] Documentation updates for AWS Batch. +* api-change:``iotevents-data``: [``botocore``] Introducing new API for deleting detectors: BatchDeleteDetector. +* api-change:``transfer``: [``botocore``] AWS Transfer Family now supports SetStat server configuration option, which provides the ability to ignore SetStat command issued by file transfer clients, enabling customers to upload files without any errors. + + +1.23.2 +====== + +* api-change:``kms``: [``botocore``] Add HMAC best practice tip, annual rotation of AWS managed keys. +* api-change:``glue``: [``botocore``] This release adds a new optional parameter called codeGenNodeConfiguration to CRUD job APIs that allows users to manage visual jobs via APIs. The updated CreateJob and UpdateJob will create jobs that can be viewed in Glue Studio as a visual graph. GetJob can be used to get codeGenNodeConfiguration. + + +1.23.1 +====== + +* api-change:``resiliencehub``: [``botocore``] In this release, we are introducing support for Amazon Elastic Container Service, Amazon Route 53, AWS Elastic Disaster Recovery, AWS Backup in addition to the existing supported Services. This release also supports Terraform file input from S3 and scheduling daily assessments +* api-change:``servicecatalog``: [``botocore``] Updated the descriptions for the ListAcceptedPortfolioShares API description and the PortfolioShareType parameters. +* api-change:``discovery``: [``botocore``] Add Migration Evaluator Collector details to the GetDiscoverySummary API response +* api-change:``sts``: [``botocore``] Documentation updates for AWS Security Token Service. +* api-change:``workspaces-web``: [``botocore``] Amazon WorkSpaces Web now supports Administrator timeout control +* api-change:``rekognition``: [``botocore``] Documentation updates for Amazon Rekognition. +* api-change:``cloudfront``: [``botocore``] Introduced a new error (TooLongCSPInResponseHeadersPolicy) that is returned when the value of the Content-Security-Policy header in a response headers policy exceeds the maximum allowed length. + + +1.23.0 +====== + +* feature:Loaders: [``botocore``] Support for loading gzip compressed model files. +* api-change:``grafana``: [``botocore``] This release adds APIs for creating and deleting API keys in an Amazon Managed Grafana workspace. + + +1.22.13 +======= + +* api-change:``ivschat``: [``botocore``] Documentation-only updates for IVS Chat API Reference. +* api-change:``lambda``: [``botocore``] Lambda releases NodeJs 16 managed runtime to be available in all commercial regions. +* api-change:``kendra``: [``botocore``] Amazon Kendra now provides a data source connector for Jira. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-jira.html +* api-change:``transfer``: [``botocore``] AWS Transfer Family now accepts ECDSA keys for server host keys +* api-change:``iot``: [``botocore``] Documentation update for China region ListMetricValues for IoT +* api-change:``workspaces``: [``botocore``] Increased the character limit of the login message from 600 to 850 characters. +* api-change:``finspace-data``: [``botocore``] We've now deprecated CreateSnapshot permission for creating a data view, instead use CreateDataView permission. +* api-change:``lightsail``: [``botocore``] This release adds support to include inactive database bundles in the response of the GetRelationalDatabaseBundles request. +* api-change:``outposts``: [``botocore``] Documentation updates for AWS Outposts. +* api-change:``ec2``: [``botocore``] This release introduces a target type Gateway Load Balancer Endpoint for mirrored traffic. Customers can now specify GatewayLoadBalancerEndpoint option during the creation of a traffic mirror target. +* api-change:``ssm-incidents``: [``botocore``] Adding support for dynamic SSM Runbook parameter values. Updating validation pattern for engagements. Adding ConflictException to UpdateReplicationSet API contract. + + +1.22.12 +======= + +* api-change:``secretsmanager``: [``botocore``] Doc only update for Secrets Manager that fixes several customer-reported issues. +* api-change:``ec2``: [``botocore``] This release updates AWS PrivateLink APIs to support IPv6 for PrivateLink Services and Endpoints of type 'Interface'. + + +1.22.11 +======= + +* api-change:``migration-hub-refactor-spaces``: [``botocore``] AWS Migration Hub Refactor Spaces documentation only update to fix a formatting issue. +* api-change:``ec2``: [``botocore``] Added support for using NitroTPM and UEFI Secure Boot on EC2 instances. +* api-change:``emr``: [``botocore``] Update emr client to latest version +* api-change:``compute-optimizer``: [``botocore``] Documentation updates for Compute Optimizer +* api-change:``eks``: [``botocore``] Adds BOTTLEROCKET_ARM_64_NVIDIA and BOTTLEROCKET_x86_64_NVIDIA AMI types to EKS managed nodegroups + + +1.22.10 +======= + +* api-change:``evidently``: [``botocore``] Add detail message inside GetExperimentResults API response to indicate experiment result availability +* api-change:``ssm-contacts``: [``botocore``] Fixed an error in the DescribeEngagement example for AWS Incident Manager. +* api-change:``cloudcontrol``: [``botocore``] SDK release for Cloud Control API to include paginators for Python SDK. + + +1.22.9 +====== + +* api-change:``rds``: [``botocore``] Various documentation improvements. +* api-change:``redshift``: [``botocore``] Introduces new field 'LoadSampleData' in CreateCluster operation. Customers can now specify 'LoadSampleData' option during creation of a cluster, which results in loading of sample data in the cluster that is created. +* api-change:``ec2``: [``botocore``] Add new state values for IPAMs, IPAM Scopes, and IPAM Pools. +* api-change:``mediapackage``: [``botocore``] This release adds Dvb Dash 2014 as an available profile option for Dash Origin Endpoints. +* api-change:``securityhub``: [``botocore``] Documentation updates for Security Hub API reference +* api-change:``location``: [``botocore``] Amazon Location Service now includes a MaxResults parameter for ListGeofences requests. + + +1.22.8 +====== + +* api-change:``ec2``: [``botocore``] Amazon EC2 I4i instances are powered by 3rd generation Intel Xeon Scalable processors and feature up to 30 TB of local AWS Nitro SSD storage +* api-change:``kendra``: [``botocore``] AWS Kendra now supports hierarchical facets for a query. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/filtering.html +* api-change:``iot``: [``botocore``] AWS IoT Jobs now allows you to create up to 100,000 active continuous and snapshot jobs by using concurrency control. +* api-change:``datasync``: [``botocore``] AWS DataSync now supports a new ObjectTags Task API option that can be used to control whether Object Tags are transferred. + + +1.22.7 +====== + +* api-change:``ssm``: [``botocore``] This release adds the TargetMaps parameter in SSM State Manager API. +* api-change:``backup``: [``botocore``] Adds support to 2 new filters about job complete time for 3 list jobs APIs in AWS Backup +* api-change:``lightsail``: [``botocore``] Documentation updates for Lightsail +* api-change:``iotsecuretunneling``: [``botocore``] This release introduces a new API RotateTunnelAccessToken that allow revoking the existing tokens and generate new tokens + + +1.22.6 +====== + +* api-change:``ec2``: [``botocore``] Adds support for allocating Dedicated Hosts on AWS Outposts. The AllocateHosts API now accepts an OutpostArn request parameter, and the DescribeHosts API now includes an OutpostArn response parameter. +* api-change:``s3``: [``botocore``] Documentation only update for doc bug fixes for the S3 API docs. +* api-change:``kinesisvideo``: [``botocore``] Add support for multiple image feature related APIs for configuring image generation and notification of a video stream. Add "GET_IMAGES" to the list of supported API names for the GetDataEndpoint API. +* api-change:``sagemaker``: [``botocore``] SageMaker Autopilot adds new metrics for all candidate models generated by Autopilot experiments; RStudio on SageMaker now allows users to bring your own development environment in a custom image. +* api-change:``kinesis-video-archived-media``: [``botocore``] Add support for GetImages API for retrieving images from a video stream + + +1.22.5 +====== + +* api-change:``organizations``: [``botocore``] This release adds the INVALID_PAYMENT_INSTRUMENT as a fail reason and an error message. +* api-change:``synthetics``: [``botocore``] CloudWatch Synthetics has introduced a new feature to provide customers with an option to delete the underlying resources that Synthetics canary creates when the user chooses to delete the canary. +* api-change:``outposts``: [``botocore``] This release adds a new API called ListAssets to the Outposts SDK, which lists the hardware assets in an Outpost. + + +1.22.4 +====== + +* api-change:``rds``: [``botocore``] Feature - Adds support for Internet Protocol Version 6 (IPv6) on RDS database instances. +* api-change:``codeguru-reviewer``: [``botocore``] Amazon CodeGuru Reviewer now supports suppressing recommendations from being generated on specific files and directories. +* api-change:``ssm``: [``botocore``] Update the StartChangeRequestExecution, adding TargetMaps to the Runbook parameter +* api-change:``mediaconvert``: [``botocore``] AWS Elemental MediaConvert SDK nows supports creation of Dolby Vision profile 8.1, the ability to generate black frames of video, and introduces audio-only DASH and CMAF support. +* api-change:``wafv2``: [``botocore``] You can now inspect all request headers and all cookies. You can now specify how to handle oversize body contents in your rules that inspect the body. + + +1.22.3 +====== + +* api-change:``auditmanager``: [``botocore``] This release adds documentation updates for Audit Manager. We provided examples of how to use the Custom_ prefix for the keywordValue attribute. We also provided more details about the DeleteAssessmentReport operation. +* api-change:``network-firewall``: [``botocore``] AWS Network Firewall adds support for stateful threat signature AWS managed rule groups. +* api-change:``ec2``: [``botocore``] This release adds support to query the public key and creation date of EC2 Key Pairs. Additionally, the format (pem or ppk) of a key pair can be specified when creating a new key pair. +* api-change:``braket``: [``botocore``] This release enables Braket Hybrid Jobs with Embedded Simulators to have multiple instances. +* api-change:``guardduty``: [``botocore``] Documentation update for API description. +* api-change:``connect``: [``botocore``] This release introduces an API for changing the current agent status of a user in Connect. + + +1.22.2 +====== + +* api-change:``rekognition``: [``botocore``] This release adds support to configure stream-processor resources for label detections on streaming-videos. UpateStreamProcessor API is also launched with this release, which could be used to update an existing stream-processor. +* api-change:``cloudtrail``: [``botocore``] Increases the retention period maximum to 2557 days. Deprecates unused fields of the ListEventDataStores API response. Updates documentation. +* api-change:``lookoutequipment``: [``botocore``] This release adds the following new features: 1) Introduces an option for automatic schema creation 2) Now allows for Ingestion of data containing most common errors and allows automatic data cleaning 3) Introduces new API ListSensorStatistics that gives further information about the ingested data +* api-change:``iotwireless``: [``botocore``] Add list support for event configurations, allow to get and update event configurations by resource type, support LoRaWAN events; Make NetworkAnalyzerConfiguration as a resource, add List, Create, Delete API support; Add FCntStart attribute support for ABP WirelessDevice. +* api-change:``amplify``: [``botocore``] Documentation only update to support the Amplify GitHub App feature launch +* api-change:``chime-sdk-media-pipelines``: [``botocore``] For Amazon Chime SDK meetings, the Amazon Chime Media Pipelines SDK allows builders to capture audio, video, and content share streams. You can also capture meeting events, live transcripts, and data messages. The pipelines save the artifacts to an Amazon S3 bucket that you designate. +* api-change:``sagemaker``: [``botocore``] Amazon SageMaker Autopilot adds support for custom validation dataset and validation ratio through the CreateAutoMLJob and DescribeAutoMLJob APIs. + + +1.22.1 +====== + +* api-change:``lightsail``: [``botocore``] This release adds support for Lightsail load balancer HTTP to HTTPS redirect and TLS policy configuration. +* api-change:``sagemaker``: [``botocore``] SageMaker Inference Recommender now accepts customer KMS key ID for encryption of endpoints and compilation outputs created during inference recommendation. +* api-change:``pricing``: [``botocore``] Documentation updates for Price List API +* api-change:``glue``: [``botocore``] This release adds documentation for the APIs to create, read, delete, list, and batch read of AWS Glue custom patterns, and for Lake Formation configuration settings in the AWS Glue crawler. +* api-change:``cloudfront``: [``botocore``] CloudFront now supports the Server-Timing header in HTTP responses sent from CloudFront. You can use this header to view metrics that help you gain insights about the behavior and performance of CloudFront. To use this header, enable it in a response headers policy. +* api-change:``ivschat``: [``botocore``] Adds new APIs for IVS Chat, a feature for building interactive chat experiences alongside an IVS broadcast. +* api-change:``network-firewall``: [``botocore``] AWS Network Firewall now enables customers to use a customer managed AWS KMS key for the encryption of their firewall resources. + + +1.22.0 +====== + +* api-change:``gamelift``: [``botocore``] Documentation updates for Amazon GameLift. +* api-change:``mq``: [``botocore``] This release adds the CRITICAL_ACTION_REQUIRED broker state and the ActionRequired API property. CRITICAL_ACTION_REQUIRED informs you when your broker is degraded. ActionRequired provides you with a code which you can use to find instructions in the Developer Guide on how to resolve the issue. +* feature:IMDS: [``botocore``] Added resiliency mechanisms to IMDS Credential Fetcher +* api-change:``securityhub``: [``botocore``] Security Hub now lets you opt-out of auto-enabling the defaults standards (CIS and FSBP) in accounts that are auto-enabled with Security Hub via Security Hub's integration with AWS Organizations. +* api-change:``connect``: [``botocore``] This release adds SearchUsers API which can be used to search for users with a Connect Instance +* api-change:``rds-data``: [``botocore``] Support to receive SQL query results in the form of a simplified JSON string. This enables developers using the new JSON string format to more easily convert it to an object using popular JSON string parsing libraries. + + +1.21.46 +======= + +* api-change:``chime-sdk-meetings``: [``botocore``] Include additional exceptions types. +* api-change:``ec2``: [``botocore``] Adds support for waiters that automatically poll for a deleted NAT Gateway until it reaches the deleted state. + + +1.21.45 +======= + +* api-change:``wisdom``: [``botocore``] This release updates the GetRecommendations API to include a trigger event list for classifying and grouping recommendations. +* api-change:``elasticache``: [``botocore``] Doc only update for ElastiCache +* api-change:``iottwinmaker``: [``botocore``] General availability (GA) for AWS IoT TwinMaker. For more information, see https://docs.aws.amazon.com/iot-twinmaker/latest/apireference/Welcome.html +* api-change:``secretsmanager``: [``botocore``] Documentation updates for Secrets Manager +* api-change:``mediatailor``: [``botocore``] This release introduces tiered channels and adds support for live sources. Customers using a STANDARD channel can now create programs using live sources. +* api-change:``storagegateway``: [``botocore``] This release adds support for minimum of 5 character length virtual tape barcodes. +* api-change:``lookoutmetrics``: [``botocore``] Added DetectMetricSetConfig API for detecting configuration required for creating metric set from provided S3 data source. +* api-change:``iotsitewise``: [``botocore``] This release adds 3 new batch data query APIs : BatchGetAssetPropertyValue, BatchGetAssetPropertyValueHistory and BatchGetAssetPropertyAggregates +* api-change:``glue``: [``botocore``] This release adds APIs to create, read, delete, list, and batch read of Glue custom entity types + + +1.21.44 +======= + +* api-change:``macie2``: [``botocore``] Sensitive data findings in Amazon Macie now indicate how Macie found the sensitive data that produced a finding (originType). +* api-change:``rds``: [``botocore``] Added a new cluster-level attribute to set the capacity range for Aurora Serverless v2 instances. +* api-change:``mgn``: [``botocore``] Removed required annotation from input fields in Describe operations requests. Added quotaValue to ServiceQuotaExceededException +* api-change:``connect``: [``botocore``] This release adds APIs to search, claim, release, list, update, and describe phone numbers. You can also use them to associate and disassociate contact flows to phone numbers. + + +1.21.43 +======= + +* api-change:``textract``: [``botocore``] This release adds support for specifying and extracting information from documents using the Queries feature within Analyze Document API +* api-change:``worklink``: [``botocore``] Amazon WorkLink is no longer supported. This will be removed in a future version of the SDK. +* api-change:``ssm``: [``botocore``] Added offset support for specifying the number of days to wait after the date and time specified by a CRON expression when creating SSM association. +* api-change:``autoscaling``: [``botocore``] EC2 Auto Scaling now adds default instance warm-up times for all scaling activities, health check replacements, and other replacement events in the Auto Scaling instance lifecycle. +* api-change:``personalize``: [``botocore``] Adding StartRecommender and StopRecommender APIs for Personalize. +* api-change:``kendra``: [``botocore``] Amazon Kendra now provides a data source connector for Quip. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-quip.html +* api-change:``polly``: [``botocore``] Amazon Polly adds new Austrian German voice - Hannah. Hannah is available as Neural voice only. +* api-change:``transfer``: [``botocore``] This release contains corrected HomeDirectoryMappings examples for several API functions: CreateAccess, UpdateAccess, CreateUser, and UpdateUser,. +* api-change:``kms``: [``botocore``] Adds support for KMS keys and APIs that generate and verify HMAC codes +* api-change:``redshift``: [``botocore``] Introduces new fields for LogDestinationType and LogExports on EnableLogging requests and Enable/Disable/DescribeLogging responses. Customers can now select CloudWatch Logs as a destination for their Audit Logs. + + +1.21.42 +======= + +* api-change:``lightsail``: [``botocore``] This release adds support to describe the synchronization status of the account-level block public access feature for your Amazon Lightsail buckets. +* api-change:``rds``: [``botocore``] Removes Amazon RDS on VMware with the deletion of APIs related to Custom Availability Zones and Media installation +* api-change:``athena``: [``botocore``] This release adds subfields, ErrorMessage, Retryable, to the AthenaError response object in the GetQueryExecution API when a query fails. + + +1.21.41 +======= + +* api-change:``batch``: [``botocore``] Enables configuration updates for compute environments with BEST_FIT_PROGRESSIVE and SPOT_CAPACITY_OPTIMIZED allocation strategies. +* api-change:``ec2``: [``botocore``] Documentation updates for Amazon EC2. +* api-change:``cloudwatch``: [``botocore``] Update cloudwatch client to latest version +* api-change:``appstream``: [``botocore``] Includes updates for create and update fleet APIs to manage the session scripts locations for Elastic fleets. +* api-change:``glue``: [``botocore``] Auto Scaling for Glue version 3.0 and later jobs to dynamically scale compute resources. This SDK change provides customers with the auto-scaled DPU usage +* api-change:``appflow``: [``botocore``] Enables users to pass custom token URL parameters for Oauth2 authentication during create connector profile + + +1.21.40 +======= + +* api-change:``cloudwatch``: [``botocore``] Update cloudwatch client to latest version +* api-change:``fsx``: [``botocore``] This release adds support for deploying FSx for ONTAP file systems in a single Availability Zone. + + +1.21.39 +======= + +* api-change:``ec2``: [``botocore``] X2idn and X2iedn instances are powered by 3rd generation Intel Xeon Scalable processors with an all-core turbo frequency up to 3.5 GHzAmazon EC2. C6a instances are powered by 3rd generation AMD EPYC processors. +* api-change:``devops-guru``: [``botocore``] This release adds new APIs DeleteInsight to deletes the insight along with the associated anomalies, events and recommendations. +* api-change:``efs``: [``botocore``] Update efs client to latest version +* api-change:``iottwinmaker``: [``botocore``] This release adds the following new features: 1) ListEntities API now supports search using ExternalId. 2) BatchPutPropertyValue and GetPropertyValueHistory API now allows users to represent time in sub-second level precisions. + + +1.21.38 +======= + +* api-change:``amplifyuibuilder``: [``botocore``] In this release, we have added the ability to bind events to component level actions. +* api-change:``apprunner``: [``botocore``] This release adds tracing for App Runner services with X-Ray using AWS Distro for OpenTelemetry. New APIs: CreateObservabilityConfiguration, DescribeObservabilityConfiguration, ListObservabilityConfigurations, and DeleteObservabilityConfiguration. Updated APIs: CreateService and UpdateService. +* api-change:``workspaces``: [``botocore``] Added API support that allows customers to create GPU-enabled WorkSpaces using EC2 G4dn instances. + + +1.21.37 +======= + +* api-change:``mediaconvert``: [``botocore``] AWS Elemental MediaConvert SDK has added support for the pass-through of WebVTT styling to WebVTT outputs, pass-through of KLV metadata to supported formats, and improved filter support for processing 444/RGB content. +* api-change:``wafv2``: [``botocore``] Add a new CurrentDefaultVersion field to ListAvailableManagedRuleGroupVersions API response; add a new VersioningSupported boolean to each ManagedRuleGroup returned from ListAvailableManagedRuleGroups API response. +* api-change:``mediapackage-vod``: [``botocore``] This release adds ScteMarkersSource as an available field for Dash Packaging Configurations. When set to MANIFEST, MediaPackage will source the SCTE-35 markers from the manifest. When set to SEGMENTS, MediaPackage will source the SCTE-35 markers from the segments. + + +1.21.36 +======= + +* api-change:``apigateway``: [``botocore``] ApiGateway CLI command get-usage now includes usagePlanId, startDate, and endDate fields in the output to match documentation. +* api-change:``personalize``: [``botocore``] This release provides tagging support in AWS Personalize. +* api-change:``pi``: [``botocore``] Adds support for DocumentDB to the Performance Insights API. +* api-change:``events``: [``botocore``] Update events client to latest version +* api-change:``docdb``: [``botocore``] Added support to enable/disable performance insights when creating or modifying db instances +* api-change:``sagemaker``: [``botocore``] Amazon Sagemaker Notebook Instances now supports G5 instance types + + +1.21.35 +======= + +* bugfix:Proxy: [``botocore``] Fix failure case for IP proxy addresses using TLS-in-TLS. `boto/botocore#2652 `__ +* api-change:``config``: [``botocore``] Add resourceType enums for AWS::EMR::SecurityConfiguration and AWS::SageMaker::CodeRepository +* api-change:``panorama``: [``botocore``] Added Brand field to device listings. +* api-change:``lambda``: [``botocore``] This release adds new APIs for creating and managing Lambda Function URLs and adds a new FunctionUrlAuthType parameter to the AddPermission API. Customers can use Function URLs to create built-in HTTPS endpoints on their functions. +* api-change:``kendra``: [``botocore``] Amazon Kendra now provides a data source connector for Box. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-box.html + + +1.21.34 +======= + +* api-change:``securityhub``: [``botocore``] Added additional ASFF details for RdsSecurityGroup AutoScalingGroup, ElbLoadBalancer, CodeBuildProject and RedshiftCluster. +* api-change:``fsx``: [``botocore``] Provide customers more visibility into file system status by adding new "Misconfigured Unavailable" status for Amazon FSx for Windows File Server. +* api-change:``s3control``: [``botocore``] Documentation-only update for doc bug fixes for the S3 Control API docs. +* api-change:``datasync``: [``botocore``] AWS DataSync now supports Amazon FSx for OpenZFS locations. + + +1.21.33 +======= + +* api-change:``iot``: [``botocore``] AWS IoT - AWS IoT Device Defender adds support to list metric datapoints collected for IoT devices through the ListMetricValues API +* api-change:``servicecatalog``: [``botocore``] This release adds ProvisioningArtifictOutputKeys to DescribeProvisioningParameters to reference the outputs of a Provisioned Product and deprecates ProvisioningArtifactOutputs. +* api-change:``sms``: [``botocore``] Revised product update notice for SMS console deprecation. +* api-change:``proton``: [``botocore``] SDK release to support tagging for AWS Proton Repository resource +* enhancement:AWSCRT: [``botocore``] Upgrade awscrt version to 0.13.8 + + +1.21.32 +======= + +* api-change:``connect``: [``botocore``] This release updates these APIs: UpdateInstanceAttribute, DescribeInstanceAttribute and ListInstanceAttributes. You can use it to programmatically enable/disable multi-party conferencing using attribute type MULTI_PARTY_CONFERENCING on the specified Amazon Connect instance. + + +1.21.31 +======= + +* api-change:``cloudcontrol``: [``botocore``] SDK release for Cloud Control API in Amazon Web Services China (Beijing) Region, operated by Sinnet, and Amazon Web Services China (Ningxia) Region, operated by NWCD +* api-change:``pinpoint-sms-voice-v2``: [``botocore``] Amazon Pinpoint now offers a version 2.0 suite of SMS and voice APIs, providing increased control over sending and configuration. This release is a new SDK for sending SMS and voice messages called PinpointSMSVoiceV2. +* api-change:``workspaces``: [``botocore``] Added APIs that allow you to customize the logo, login message, and help links in the WorkSpaces client login page. To learn more, visit https://docs.aws.amazon.com/workspaces/latest/adminguide/customize-branding.html +* api-change:``route53-recovery-cluster``: [``botocore``] This release adds a new API "ListRoutingControls" to list routing control states using the highly reliable Route 53 ARC data plane endpoints. +* api-change:``databrew``: [``botocore``] This AWS Glue Databrew release adds feature to support ORC as an input format. +* api-change:``auditmanager``: [``botocore``] This release adds documentation updates for Audit Manager. The updates provide data deletion guidance when a customer deregisters Audit Manager or deregisters a delegated administrator. +* api-change:``grafana``: [``botocore``] This release adds tagging support to the Managed Grafana service. New APIs: TagResource, UntagResource and ListTagsForResource. Updates: add optional field tags to support tagging while calling CreateWorkspace. + + +1.21.30 +======= + +* api-change:``iot-data``: [``botocore``] Update the default AWS IoT Core Data Plane endpoint from VeriSign signed to ATS signed. If you have firewalls with strict egress rules, configure the rules to grant you access to data-ats.iot.[region].amazonaws.com or data-ats.iot.[region].amazonaws.com.cn. +* api-change:``ec2``: [``botocore``] This release simplifies the auto-recovery configuration process enabling customers to set the recovery behavior to disabled or default +* api-change:``fms``: [``botocore``] AWS Firewall Manager now supports the configuration of third-party policies that can use either the centralized or distributed deployment models. +* api-change:``fsx``: [``botocore``] This release adds support for modifying throughput capacity for FSx for ONTAP file systems. +* api-change:``iot``: [``botocore``] Doc only update for IoT that fixes customer-reported issues. + + +1.21.29 +======= + +* api-change:``organizations``: [``botocore``] This release provides the new CloseAccount API that enables principals in the management account to close any member account within an organization. + + +1.21.28 +======= + +* api-change:``medialive``: [``botocore``] This release adds support for selecting a maintenance window. +* api-change:``acm-pca``: [``botocore``] Updating service name entities + + +1.21.27 +======= + +* api-change:``ec2``: [``botocore``] This is release adds support for Amazon VPC Reachability Analyzer to analyze path through a Transit Gateway. +* api-change:``ssm``: [``botocore``] This Patch Manager release supports creating, updating, and deleting Patch Baselines for Rocky Linux OS. +* api-change:``batch``: [``botocore``] Bug Fix: Fixed a bug where shapes were marked as unboxed and were not serialized and sent over the wire, causing an API error from the service. + + +1.21.26 +======= + +* api-change:``lambda``: [``botocore``] Adds support for increased ephemeral storage (/tmp) up to 10GB for Lambda functions. Customers can now provision up to 10 GB of ephemeral storage per function instance, a 20x increase over the previous limit of 512 MB. +* api-change:``config``: [``botocore``] Added new APIs GetCustomRulePolicy and GetOrganizationCustomRulePolicy, and updated existing APIs PutConfigRule, DescribeConfigRule, DescribeConfigRuleEvaluationStatus, PutOrganizationConfigRule, DescribeConfigRule to support a new feature for building AWS Config rules with AWS CloudFormation Guard +* api-change:``transcribe``: [``botocore``] This release adds an additional parameter for subtitling with Amazon Transcribe batch jobs: outputStartIndex. + + +1.21.25 +======= + +* api-change:``redshift``: [``botocore``] This release adds a new [--encrypted | --no-encrypted] field in restore-from-cluster-snapshot API. Customers can now restore an unencrypted snapshot to a cluster encrypted with AWS Managed Key or their own KMS key. +* api-change:``ebs``: [``botocore``] Increased the maximum supported value for the Timeout parameter of the StartSnapshot API from 60 minutes to 4320 minutes. Changed the HTTP error code for ConflictException from 503 to 409. +* api-change:``gamesparks``: [``botocore``] Released the preview of Amazon GameSparks, a fully managed AWS service that provides a multi-service backend for game developers. +* api-change:``elasticache``: [``botocore``] Doc only update for ElastiCache +* api-change:``transfer``: [``botocore``] Documentation updates for AWS Transfer Family to describe how to remove an associated workflow from a server. +* api-change:``auditmanager``: [``botocore``] This release updates 1 API parameter, the SnsArn attribute. The character length and regex pattern for the SnsArn attribute have been updated, which enables you to deselect an SNS topic when using the UpdateSettings operation. +* api-change:``ssm``: [``botocore``] Update AddTagsToResource, ListTagsForResource, and RemoveTagsFromResource APIs to reflect the support for tagging Automation resources. Includes other minor documentation updates. + + +1.21.24 +======= + +* api-change:``location``: [``botocore``] Amazon Location Service now includes a MaxResults parameter for GetDevicePositionHistory requests. +* api-change:``polly``: [``botocore``] Amazon Polly adds new Catalan voice - Arlet. Arlet is available as Neural voice only. +* api-change:``lakeformation``: [``botocore``] The release fixes the incorrect permissions called out in the documentation - DESCRIBE_TAG, ASSOCIATE_TAG, DELETE_TAG, ALTER_TAG. This trebuchet release fixes the corresponding SDK and documentation. +* api-change:``ecs``: [``botocore``] Documentation only update to address tickets +* api-change:``ce``: [``botocore``] Added three new APIs to support tagging and resource-level authorization on Cost Explorer resources: TagResource, UntagResource, ListTagsForResource. Added optional parameters to CreateCostCategoryDefinition, CreateAnomalySubscription and CreateAnomalyMonitor APIs to support Tag On Create. + + +1.21.23 +======= + +* api-change:``ram``: [``botocore``] Document improvements to the RAM API operations and parameter descriptions. +* api-change:``ecr``: [``botocore``] This release includes a fix in the DescribeImageScanFindings paginated output. +* api-change:``quicksight``: [``botocore``] AWS QuickSight Service Features - Expand public API support for group management. +* api-change:``chime-sdk-meetings``: [``botocore``] Add support for media replication to link multiple WebRTC media sessions together to reach larger and global audiences. Participants connected to a replica session can be granted access to join the primary session and can switch sessions with their existing WebRTC connection +* api-change:``mediaconnect``: [``botocore``] This release adds support for selecting a maintenance window. + + +1.21.22 +======= + +* enhancement:jmespath: [``botocore``] Add env markers to get working version of jmespath for python 3.6 +* api-change:``glue``: [``botocore``] Added 9 new APIs for AWS Glue Interactive Sessions: ListSessions, StopSession, CreateSession, GetSession, DeleteSession, RunStatement, GetStatement, ListStatements, CancelStatement + + +1.21.21 +======= + +* enhancement:Dependency: [``botocore``] Added support for jmespath 1.0 +* api-change:``amplifybackend``: [``botocore``] Adding the ability to customize Cognito verification messages for email and SMS in CreateBackendAuth and UpdateBackendAuth. Adding deprecation documentation for ForgotPassword in CreateBackendAuth and UpdateBackendAuth +* api-change:``acm-pca``: [``botocore``] AWS Certificate Manager (ACM) Private Certificate Authority (CA) now supports customizable certificate subject names and extensions. +* api-change:``ssm-incidents``: [``botocore``] Removed incorrect validation pattern for IncidentRecordSource.invokedBy +* enhancement:Dependency: Added support for jmespath 1.0 +* api-change:``billingconductor``: [``botocore``] This is the initial SDK release for AWS Billing Conductor. The AWS Billing Conductor is a customizable billing service, allowing you to customize your billing data to match your desired business structure. +* api-change:``s3outposts``: [``botocore``] S3 on Outposts is releasing a new API, ListSharedEndpoints, that lists all endpoints associated with S3 on Outpost, that has been shared by Resource Access Manager (RAM). + + +1.21.20 +======= + +* api-change:``robomaker``: [``botocore``] This release deprecates ROS, Ubuntu and Gazbeo from RoboMaker Simulation Service Software Suites in favor of user-supplied containers and Relaxed Software Suites. +* api-change:``dataexchange``: [``botocore``] This feature enables data providers to use the RevokeRevision operation to revoke subscriber access to a given revision. Subscribers are unable to interact with assets within a revoked revision. +* api-change:``ec2``: [``botocore``] Adds the Cascade parameter to the DeleteIpam API. Customers can use this parameter to automatically delete their IPAM, including non-default scopes, pools, cidrs, and allocations. There mustn't be any pools provisioned in the default public scope to use this parameter. +* api-change:``cognito-idp``: [``botocore``] Updated EmailConfigurationType and SmsConfigurationType to reflect that you can now choose Amazon SES and Amazon SNS resources in the same Region. +* enhancement:AWSCRT: [``botocore``] Upgrade awscrt extra to 0.13.5 +* api-change:``location``: [``botocore``] New HERE style "VectorHereExplore" and "VectorHereExploreTruck". +* api-change:``ecs``: [``botocore``] Documentation only update to address tickets +* api-change:``keyspaces``: [``botocore``] Fixing formatting issues in CLI and SDK documentation +* api-change:``rds``: [``botocore``] Various documentation improvements + + +1.21.19 +======= + +* api-change:``kendra``: [``botocore``] Amazon Kendra now provides a data source connector for Slack. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-slack.html +* api-change:``timestream-query``: [``botocore``] Amazon Timestream Scheduled Queries now support Timestamp datatype in a multi-measure record. +* enhancement:Stubber: [``botocore``] Added support for modeled exception fields when adding errors to a client stub. Implements boto/boto3`#3178 `__. +* api-change:``elasticache``: [``botocore``] Doc only update for ElastiCache +* api-change:``config``: [``botocore``] Add resourceType enums for AWS::ECR::PublicRepository and AWS::EC2::LaunchTemplate + + +1.21.18 +======= + +* api-change:``outposts``: [``botocore``] This release adds address filters for listSites +* api-change:``lambda``: [``botocore``] Adds PrincipalOrgID support to AddPermission API. Customers can use it to manage permissions to lambda functions at AWS Organizations level. +* api-change:``secretsmanager``: [``botocore``] Documentation updates for Secrets Manager. +* api-change:``connect``: [``botocore``] This release adds support for enabling Rich Messaging when starting a new chat session via the StartChatContact API. Rich Messaging enables the following formatting options: bold, italics, hyperlinks, bulleted lists, and numbered lists. +* api-change:``chime``: [``botocore``] Chime VoiceConnector Logging APIs will now support MediaMetricLogs. Also CreateMeetingDialOut now returns AccessDeniedException. + + +1.21.17 +======= + +* api-change:``transcribe``: [``botocore``] Documentation fix for API `StartMedicalTranscriptionJobRequest`, now showing min sample rate as 16khz +* api-change:``transfer``: [``botocore``] Adding more descriptive error types for managed workflows +* api-change:``lexv2-models``: [``botocore``] Update lexv2-models client to latest version + + +1.21.16 +======= + +* api-change:``comprehend``: [``botocore``] Amazon Comprehend now supports extracting the sentiment associated with entities such as brands, products and services from text documents. + + +1.21.15 +======= + +* api-change:``eks``: [``botocore``] Introducing a new enum for NodeGroup error code: Ec2SubnetMissingIpv6Assignment +* api-change:``keyspaces``: [``botocore``] Adding link to CloudTrail section in Amazon Keyspaces Developer Guide +* api-change:``mediaconvert``: [``botocore``] AWS Elemental MediaConvert SDK has added support for reading timecode from AVCHD sources and now provides the ability to segment WebVTT at the same interval as the video and audio in HLS packages. + + +1.21.14 +======= + +* api-change:``chime-sdk-meetings``: [``botocore``] Adds support for Transcribe language identification feature to the StartMeetingTranscription API. +* api-change:``ecs``: [``botocore``] Amazon ECS UpdateService API now supports additional parameters: loadBalancers, propagateTags, enableECSManagedTags, and serviceRegistries +* api-change:``migration-hub-refactor-spaces``: [``botocore``] AWS Migration Hub Refactor Spaces documentation update. + + +1.21.13 +======= + +* api-change:``synthetics``: [``botocore``] Allow custom handler function. +* api-change:``transfer``: [``botocore``] Add waiters for server online and offline. +* api-change:``devops-guru``: [``botocore``] Amazon DevOps Guru now integrates with Amazon CodeGuru Profiler. You can view CodeGuru Profiler recommendations for your AWS Lambda function in DevOps Guru. This feature is enabled by default for new customers as of 3/4/2022. Existing customers can enable this feature with UpdateEventSourcesConfig. +* api-change:``macie``: [``botocore``] Amazon Macie Classic (macie) has been discontinued and is no longer available. A new Amazon Macie (macie2) is now available with significant design improvements and additional features. +* api-change:``ec2``: [``botocore``] Documentation updates for Amazon EC2. +* api-change:``sts``: [``botocore``] Documentation updates for AWS Security Token Service. +* api-change:``connect``: [``botocore``] This release updates the *InstanceStorageConfig APIs so they support a new ResourceType: REAL_TIME_CONTACT_ANALYSIS_SEGMENTS. Use this resource type to enable streaming for real-time contact analysis and to associate the Kinesis stream where real-time contact analysis segments will be published. + + +1.21.12 +======= + +* api-change:``greengrassv2``: [``botocore``] Doc only update that clarifies Create Deployment section. +* api-change:``fsx``: [``botocore``] This release adds support for data repository associations to use root ("/") as the file system path +* api-change:``kendra``: [``botocore``] Amazon Kendra now suggests spell corrections for a query. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/query-spell-check.html +* api-change:``appflow``: [``botocore``] Launching Amazon AppFlow Marketo as a destination connector SDK. +* api-change:``timestream-query``: [``botocore``] Documentation only update for SDK and CLI + + +1.21.11 +======= + +* api-change:``gamelift``: [``botocore``] Minor updates to address errors. +* api-change:``cloudtrail``: [``botocore``] Add bytesScanned field into responses of DescribeQuery and GetQueryResults. +* api-change:``athena``: [``botocore``] This release adds support for S3 Object Ownership by allowing the S3 bucket owner full control canned ACL to be set when Athena writes query results to S3 buckets. +* api-change:``keyspaces``: [``botocore``] This release adds support for data definition language (DDL) operations +* api-change:``ecr``: [``botocore``] This release adds support for tracking images lastRecordedPullTime. + + +1.21.10 +======= + +* api-change:``mediapackage``: [``botocore``] This release adds Hybridcast as an available profile option for Dash Origin Endpoints. +* api-change:``rds``: [``botocore``] Documentation updates for Multi-AZ DB clusters. +* api-change:``mgn``: [``botocore``] Add support for GP3 and IO2 volume types. Add bootMode to LaunchConfiguration object (and as a parameter to UpdateLaunchConfigurationRequest). +* api-change:``kafkaconnect``: [``botocore``] Adds operation for custom plugin deletion (DeleteCustomPlugin) and adds new StateDescription field to DescribeCustomPlugin and DescribeConnector responses to return errors from asynchronous resource creation. + + +1.21.9 +====== + +* api-change:``finspace-data``: [``botocore``] Add new APIs for managing Users and Permission Groups. +* api-change:``amplify``: [``botocore``] Add repositoryCloneMethod field for hosting an Amplify app. This field shows what authorization method is used to clone the repo: SSH, TOKEN, or SIGV4. +* api-change:``fsx``: [``botocore``] This release adds support for the following FSx for OpenZFS features: snapshot lifecycle transition messages, force flag for deleting file systems with child resources, LZ4 data compression, custom record sizes, and unsetting volume quotas and reservations. +* api-change:``fis``: [``botocore``] This release adds logging support for AWS Fault Injection Simulator experiments. Experiment templates can now be configured to send experiment activity logs to Amazon CloudWatch Logs or to an S3 bucket. +* api-change:``route53-recovery-cluster``: [``botocore``] This release adds a new API option to enable overriding safety rules to allow routing control state updates. +* api-change:``amplifyuibuilder``: [``botocore``] We are adding the ability to configure workflows and actions for components. +* api-change:``athena``: [``botocore``] This release adds support for updating an existing named query. +* api-change:``ec2``: [``botocore``] This release adds support for new AMI property 'lastLaunchedTime' +* api-change:``servicecatalog-appregistry``: [``botocore``] AppRegistry is deprecating Application and Attribute-Group Name update feature. In this release, we are marking the name attributes for Update APIs as deprecated to give a heads up to our customers. + + +1.21.8 +====== + +* api-change:``elasticache``: [``botocore``] Doc only update for ElastiCache +* api-change:``panorama``: [``botocore``] Added NTP server configuration parameter to ProvisionDevice operation. Added alternate software fields to DescribeDevice response + + +1.21.7 +====== + +* api-change:``route53``: [``botocore``] SDK doc update for Route 53 to update some parameters with new information. +* api-change:``databrew``: [``botocore``] This AWS Glue Databrew release adds feature to merge job outputs into a max number of files for S3 File output type. +* api-change:``transfer``: [``botocore``] Support automatic pagination when listing AWS Transfer Family resources. +* api-change:``s3control``: [``botocore``] Amazon S3 Batch Operations adds support for new integrity checking capabilities in Amazon S3. +* api-change:``s3``: [``botocore``] This release adds support for new integrity checking capabilities in Amazon S3. You can choose from four supported checksum algorithms for data integrity checking on your upload and download requests. In addition, AWS SDK can automatically calculate a checksum as it streams data into S3 +* api-change:``fms``: [``botocore``] AWS Firewall Manager now supports the configuration of AWS Network Firewall policies with either centralized or distributed deployment models. This release also adds support for custom endpoint configuration, where you can choose which Availability Zones to create firewall endpoints in. +* api-change:``lightsail``: [``botocore``] This release adds support to delete and create Lightsail default key pairs that you can use with Lightsail instances. +* api-change:``autoscaling``: [``botocore``] You can now hibernate instances in a warm pool to stop instances without deleting their RAM contents. You can now also return instances to the warm pool on scale in, instead of always terminating capacity that you will need later. + + +1.21.6 +====== + +* api-change:``transfer``: [``botocore``] The file input selection feature provides the ability to use either the originally uploaded file or the output file from the previous workflow step, enabling customers to make multiple copies of the original file while keeping the source file intact for file archival. +* api-change:``lambda``: [``botocore``] Lambda releases .NET 6 managed runtime to be available in all commercial regions. +* api-change:``textract``: [``botocore``] Added support for merged cells and column header for table response. + + +1.21.5 +====== + +* api-change:``translate``: [``botocore``] This release enables customers to use translation settings for formality customization in their synchronous translation output. +* api-change:``wafv2``: [``botocore``] Updated descriptions for logging configuration. +* api-change:``apprunner``: [``botocore``] AWS App Runner adds a Java platform (Corretto 8, Corretto 11 runtimes) and a Node.js 14 runtime. + + +1.21.4 +====== + +* api-change:``imagebuilder``: [``botocore``] This release adds support to enable faster launching for Windows AMIs created by EC2 Image Builder. +* api-change:``customer-profiles``: [``botocore``] This release introduces apis CreateIntegrationWorkflow, DeleteWorkflow, ListWorkflows, GetWorkflow and GetWorkflowSteps. These apis are used to manage and view integration workflows. +* api-change:``dynamodb``: [``botocore``] DynamoDB ExecuteStatement API now supports Limit as a request parameter to specify the maximum number of items to evaluate. If specified, the service will process up to the Limit and the results will include a LastEvaluatedKey value to continue the read in a subsequent operation. + + +1.21.3 +====== + +* api-change:``transfer``: [``botocore``] Properties for Transfer Family used with SFTP, FTP, and FTPS protocols. Display Banners are bodies of text that can be displayed before and/or after a user authenticates onto a server using one of the previously mentioned protocols. +* api-change:``gamelift``: [``botocore``] Increase string list limit from 10 to 100. +* api-change:``budgets``: [``botocore``] This change introduces DescribeBudgetNotificationsForAccount API which returns budget notifications for the specified account + + +1.21.2 +====== + +* api-change:``iam``: [``botocore``] Documentation updates for AWS Identity and Access Management (IAM). +* api-change:``redshift``: [``botocore``] SDK release for Cross region datasharing and cost-control for cross region datasharing +* api-change:``evidently``: [``botocore``] Add support for filtering list of experiments and launches by status +* api-change:``backup``: [``botocore``] AWS Backup add new S3_BACKUP_OBJECT_FAILED and S3_RESTORE_OBJECT_FAILED event types in BackupVaultNotifications events list. + + +1.21.1 +====== + +* api-change:``ec2``: [``botocore``] Documentation updates for EC2. +* api-change:``budgets``: [``botocore``] Adds support for auto-adjusting budgets, a new budget method alongside fixed and planned. Auto-adjusting budgets introduces new metadata to configure a budget limit baseline using a historical lookback average or current period forecast. +* api-change:``ce``: [``botocore``] AWS Cost Anomaly Detection now supports SNS FIFO topic subscribers. +* api-change:``glue``: [``botocore``] Support for optimistic locking in UpdateTable +* api-change:``ssm``: [``botocore``] Assorted ticket fixes and updates for AWS Systems Manager. + + +1.21.0 +====== + +* api-change:``appflow``: [``botocore``] Launching Amazon AppFlow SAP as a destination connector SDK. +* feature:Parser: [``botocore``] Adding support for parsing int/long types in rest-json response headers. +* api-change:``rds``: [``botocore``] Adds support for determining which Aurora PostgreSQL versions support Babelfish. +* api-change:``athena``: [``botocore``] This release adds a subfield, ErrorType, to the AthenaError response object in the GetQueryExecution API when a query fails. + + +1.20.54 +======= + +* api-change:``ssm``: [``botocore``] Documentation updates for AWS Systems Manager. + + +1.20.53 +======= + +* api-change:``cloudformation``: [``botocore``] This SDK release adds AWS CloudFormation Hooks HandlerErrorCodes +* api-change:``lookoutvision``: [``botocore``] This release makes CompilerOptions in Lookout for Vision's StartModelPackagingJob's Configuration object optional. +* api-change:``pinpoint``: [``botocore``] This SDK release adds a new paramater creation date for GetApp and GetApps Api call +* api-change:``sns``: [``botocore``] Customer requested typo fix in API documentation. +* api-change:``wafv2``: [``botocore``] Adds support for AWS WAF Fraud Control account takeover prevention (ATP), with configuration options for the new managed rule group AWSManagedRulesATPRuleSet and support for application integration SDKs for Android and iOS mobile apps. + + +1.20.52 +======= + +* api-change:``cloudformation``: [``botocore``] This SDK release is for the feature launch of AWS CloudFormation Hooks. + + +1.20.51 +======= + +* api-change:``kendra``: [``botocore``] Amazon Kendra now provides a data source connector for Amazon FSx. For more information, see https://docs.aws.amazon.com/kendra/latest/dg/data-source-fsx.html +* api-change:``apprunner``: [``botocore``] This release adds support for App Runner to route outbound network traffic of a service through an Amazon VPC. New API: CreateVpcConnector, DescribeVpcConnector, ListVpcConnectors, and DeleteVpcConnector. Updated API: CreateService, DescribeService, and UpdateService. +* api-change:``s3control``: [``botocore``] This release adds support for S3 Batch Replication. Batch Replication lets you replicate existing objects, already replicated objects to new destinations, and objects that previously failed to replicate. Customers will receive object-level visibility of progress and a detailed completion report. +* api-change:``sagemaker``: [``botocore``] Autopilot now generates an additional report with information on the performance of the best model, such as a Confusion matrix and Area under the receiver operating characteristic (AUC-ROC). The path to the report can be found in CandidateArtifactLocations. + + +1.20.50 +======= + +* api-change:``auditmanager``: [``botocore``] This release updates 3 API parameters. UpdateAssessmentFrameworkControlSet now requires the controls attribute, and CreateAssessmentFrameworkControl requires the id attribute. Additionally, UpdateAssessmentFramework now has a minimum length constraint for the controlSets attribute. +* api-change:``synthetics``: [``botocore``] Adding names parameters to the Describe APIs. +* api-change:``ssm-incidents``: [``botocore``] Update RelatedItem enum to support SSM Automation +* api-change:``events``: [``botocore``] Update events client to latest version +* enhancement:Lambda Request Header: [``botocore``] Adding request header for Lambda recursion detection. + + +1.20.49 +======= + +* api-change:``athena``: [``botocore``] You can now optionally specify the account ID that you expect to be the owner of your query results output location bucket in Athena. If the account ID of the query results bucket owner does not match the specified account ID, attempts to output to the bucket will fail with an S3 permissions error. +* api-change:``rds``: [``botocore``] updates for RDS Custom for Oracle 12.1 support +* api-change:``lakeformation``: [``botocore``] Add support for calling Update Table Objects without a TransactionId. + + +1.20.48 +======= + +* api-change:``ec2``: [``botocore``] adds support for AMIs in Recycle Bin +* api-change:``robomaker``: [``botocore``] The release deprecates the use various APIs of RoboMaker Deployment Service in favor of AWS IoT GreenGrass v2.0. +* api-change:``meteringmarketplace``: [``botocore``] Add CustomerAWSAccountId to ResolveCustomer API response and increase UsageAllocation limit to 2500. +* api-change:``rbin``: [``botocore``] Add EC2 Image recycle bin support. + + +1.20.47 +======= + +* api-change:``emr``: [``botocore``] Update emr client to latest version +* api-change:``personalize``: [``botocore``] Adding minRecommendationRequestsPerSecond attribute to recommender APIs. +* enhancement:Request headers: [``botocore``] Adding request headers with retry information. +* api-change:``appflow``: [``botocore``] Launching Amazon AppFlow Custom Connector SDK. +* api-change:``dynamodb``: [``botocore``] Documentation update for DynamoDB Java SDK. +* api-change:``iot``: [``botocore``] This release adds support for configuring AWS IoT logging level per client ID, source IP, or principal ID. +* api-change:``comprehend``: [``botocore``] Amazon Comprehend now supports sharing and importing custom trained models from one AWS account to another within the same region. +* api-change:``ce``: [``botocore``] Doc-only update for Cost Explorer API that adds INVOICING_ENTITY dimensions +* api-change:``fis``: [``botocore``] Added GetTargetResourceType and ListTargetResourceTypesAPI actions. These actions return additional details about resource types and parameters that can be targeted by FIS actions. Added a parameters field for the targets that can be specified in experiment templates. +* api-change:``es``: [``botocore``] Allows customers to get progress updates for blue/green deployments +* api-change:``glue``: [``botocore``] Launch Protobuf support for AWS Glue Schema Registry +* api-change:``elasticache``: [``botocore``] Documentation update for AWS ElastiCache + + +1.20.46 +======= + +* api-change:``appconfigdata``: [``botocore``] Documentation updates for AWS AppConfig Data. +* api-change:``athena``: [``botocore``] This release adds a field, AthenaError, to the GetQueryExecution response object when a query fails. +* api-change:``appconfig``: [``botocore``] Documentation updates for AWS AppConfig +* api-change:``cognito-idp``: [``botocore``] Doc updates for Cognito user pools API Reference. +* api-change:``secretsmanager``: [``botocore``] Feature are ready to release on Jan 28th +* api-change:``sagemaker``: [``botocore``] This release added a new NNA accelerator compilation support for Sagemaker Neo. + + +1.20.45 +======= + +* api-change:``ec2``: [``botocore``] X2ezn instances are powered by Intel Cascade Lake CPUs that deliver turbo all core frequency of up to 4.5 GHz and up to 100 Gbps of networking bandwidth +* api-change:``kafka``: [``botocore``] Amazon MSK has updated the CreateCluster and UpdateBrokerStorage API that allows you to specify volume throughput during cluster creation and broker volume updates. +* api-change:``connect``: [``botocore``] This release adds support for configuring a custom chat duration when starting a new chat session via the StartChatContact API. The default value for chat duration is 25 hours, minimum configurable value is 1 hour (60 minutes) and maximum configurable value is 7 days (10,080 minutes). +* api-change:``amplify``: [``botocore``] Doc only update to the description of basicauthcredentials to describe the required encoding and format. +* api-change:``opensearch``: [``botocore``] Allows customers to get progress updates for blue/green deployments + + +1.20.44 +======= + +* api-change:``frauddetector``: [``botocore``] Added new APIs for viewing past predictions and obtaining prediction metadata including prediction explanations: ListEventPredictions and GetEventPredictionMetadata +* api-change:``ebs``: [``botocore``] Documentation updates for Amazon EBS Direct APIs. +* api-change:``codeguru-reviewer``: [``botocore``] Added failure state and adjusted timeout in waiter +* api-change:``securityhub``: [``botocore``] Adding top level Sample boolean field +* api-change:``sagemaker``: [``botocore``] API changes relating to Fail steps in model building pipeline and add PipelineExecutionFailureReason in PipelineExecutionSummary. + + +1.20.43 +======= + +* api-change:``fsx``: [``botocore``] This release adds support for growing SSD storage capacity and growing/shrinking SSD IOPS for FSx for ONTAP file systems. +* api-change:``efs``: [``botocore``] Update efs client to latest version +* api-change:``connect``: [``botocore``] This release adds support for custom vocabularies to be used with Contact Lens. Custom vocabularies improve transcription accuracy for one or more specific words. +* api-change:``guardduty``: [``botocore``] Amazon GuardDuty expands threat detection coverage to protect Amazon Elastic Kubernetes Service (EKS) workloads. + + +1.20.42 +======= + +* api-change:``route53-recovery-readiness``: [``botocore``] Updated documentation for Route53 Recovery Readiness APIs. + + +1.20.41 +======= + +* enhancement:Exceptions: [``botocore``] ProxyConnectionError previously provided the full proxy URL. User info will now be appropriately masked if needed. +* api-change:``mediaconvert``: [``botocore``] AWS Elemental MediaConvert SDK has added support for 4K AV1 output resolutions & 10-bit AV1 color, the ability to ingest sidecar Dolby Vision XML metadata files, and the ability to flag WebVTT and IMSC tracks for accessibility in HLS. +* api-change:``transcribe``: [``botocore``] Add support for granular PIIEntityTypes when using Batch ContentRedaction. + + +1.20.40 +======= + +* api-change:``guardduty``: [``botocore``] Amazon GuardDuty findings now include remoteAccountDetails under AwsApiCallAction section if instance credential is exfiltrated. +* api-change:``connect``: [``botocore``] This release adds tagging support for UserHierarchyGroups resource. +* api-change:``mediatailor``: [``botocore``] This release adds support for multiple Segment Delivery Configurations. Users can provide a list of names and URLs when creating or editing a source location. When retrieving content, users can send a header to choose which URL should be used to serve content. +* api-change:``fis``: [``botocore``] Added action startTime and action endTime timestamp fields to the ExperimentAction object +* api-change:``ec2``: [``botocore``] C6i, M6i and R6i instances are powered by a third-generation Intel Xeon Scalable processor (Ice Lake) delivering all-core turbo frequency of 3.5 GHz + + +1.20.39 +======= + +* api-change:``macie2``: [``botocore``] This release of the Amazon Macie API introduces stricter validation of requests to create custom data identifiers. +* api-change:``ec2-instance-connect``: [``botocore``] Adds support for ED25519 keys. PushSSHPublicKey Availability Zone parameter is now optional. Adds EC2InstanceStateInvalidException for instances that are not running. This was previously a service exception, so this may require updating your code to handle this new exception. + + +1.20.38 +======= + +* api-change:``ivs``: [``botocore``] This release adds support for the new Thumbnail Configuration property for Recording Configurations. For more information see https://docs.aws.amazon.com/ivs/latest/userguide/record-to-s3.html +* api-change:``storagegateway``: [``botocore``] Documentation update for adding bandwidth throttling support for S3 File Gateways. +* api-change:``location``: [``botocore``] This release adds the CalculateRouteMatrix API which calculates routes for the provided departure and destination positions. The release also deprecates the use of pricing plan across all verticals. +* api-change:``cloudtrail``: [``botocore``] This release fixes a documentation bug in the description for the readOnly field selector in advanced event selectors. The description now clarifies that users omit the readOnly field selector to select both Read and Write management events. +* api-change:``ec2``: [``botocore``] Add support for AWS Client VPN client login banner and session timeout. + + +1.20.37 +======= + +* enhancement:Configuration: [``botocore``] Adding support for `defaults_mode` configuration. The `defaults_mode` will be used to determine how certain default configuration options are resolved in the SDK. + + +1.20.36 +======= + +* api-change:``config``: [``botocore``] Update ResourceType enum with values for CodeDeploy, EC2 and Kinesis resources +* api-change:``application-insights``: [``botocore``] Application Insights support for Active Directory and SharePoint +* api-change:``honeycode``: [``botocore``] Added read and write api support for multi-select picklist. And added errorcode field to DescribeTableDataImportJob API output, when import job fails. +* api-change:``ram``: [``botocore``] This release adds the ListPermissionVersions API which lists the versions for a given permission. +* api-change:``lookoutmetrics``: [``botocore``] This release adds a new DeactivateAnomalyDetector API operation. + + +1.20.35 +======= + +* api-change:``pinpoint``: [``botocore``] Adds JourneyChannelSettings to WriteJourneyRequest +* api-change:``lexv2-runtime``: [``botocore``] Update lexv2-runtime client to latest version +* api-change:``nimble``: [``botocore``] Amazon Nimble Studio now supports validation for Launch Profiles. Launch Profiles now report static validation results after create/update to detect errors in network or active directory configuration. +* api-change:``glue``: [``botocore``] This SDK release adds support to pass run properties when starting a workflow run +* api-change:``ssm``: [``botocore``] AWS Systems Manager adds category support for DescribeDocument API +* api-change:``elasticache``: [``botocore``] AWS ElastiCache for Redis has added a new Engine Log LogType in LogDelivery feature. You can now publish the Engine Log from your Amazon ElastiCache for Redis clusters to Amazon CloudWatch Logs and Amazon Kinesis Data Firehose. + + 1.20.34 ======= diff --git a/README.rst b/README.rst index 9a69bab..91bc27e 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Boto3 - The AWS SDK for Python =============================== -|Version| |Gitter| +|Version| |Python| |License| Boto3 is the Amazon Web Services (AWS) Software Development Kit (SDK) for Python, which allows Python developers to write software that makes use @@ -10,50 +10,62 @@ of services like Amazon S3 and Amazon EC2. You can find the latest, most up to date, documentation at our `doc site`_, including a list of services that are supported. -Boto3 is maintained and published by Amazon Web Services. +Boto3 is maintained and published by `Amazon Web Services`_. + +Boto (pronounced boh-toh) was named after the fresh water dolphin native to the Amazon river. The name was chosen by the author of the original Boto library, Mitch Garnaat, as a reference to the company. Notices ------- -On 01/15/2021 deprecation for Python 2.7 was announced and support was dropped -on 07/15/2021. To avoid disruption, customers using Boto3 on Python 2.7 may +On 2021-01-15, deprecation for Python 2.7 was announced and support was dropped +on 2021-07-15. To avoid disruption, customers using Boto3 on Python 2.7 may need to upgrade their version of Python or pin the version of Boto3. For more information, see this `blog post `__. +On 2022-05-30, we will be dropping support for Python 3.6. This follows the +Python Software Foundation `end of support `__ +for the runtime which occurred on 2021-12-23. +For more information, see this `blog post `__. .. _boto: https://docs.pythonboto.org/ .. _`doc site`: https://boto3.amazonaws.com/v1/documentation/api/latest/index.html -.. |Gitter| image:: https://badges.gitter.im/boto/boto3.svg - :target: https://gitter.im/boto/boto3 - :alt: Gitter -.. |Downloads| image:: http://img.shields.io/pypi/dm/boto3.svg?style=flat +.. _`Amazon Web Services`: https://aws.amazon.com/what-is-aws/ +.. |Python| image:: https://img.shields.io/pypi/pyversions/boto3.svg?style=flat :target: https://pypi.python.org/pypi/boto3/ - :alt: Downloads + :alt: Python Versions .. |Version| image:: http://img.shields.io/pypi/v/boto3.svg?style=flat :target: https://pypi.python.org/pypi/boto3/ - :alt: Version + :alt: Package Version .. |License| image:: http://img.shields.io/pypi/l/boto3.svg?style=flat :target: https://github.com/boto/boto3/blob/develop/LICENSE :alt: License Getting Started --------------- -Assuming that you have Python and ``virtualenv`` installed, set up your environment and install the required dependencies like this or you can install the library using ``pip``: +Assuming that you have a supported version of Python installed, you can first +set up your environment with: + +.. code-block:: sh + + $ python -m venv .venv + ... + $ . .venv/bin/activate + +Then, you can install boto3 from PyPI with: + +.. code-block:: sh + + $ python -m pip install boto3 + +or install from source with: .. code-block:: sh $ git clone https://github.com/boto/boto3.git $ cd boto3 - $ virtualenv venv - ... - $ . venv/bin/activate $ python -m pip install -r requirements.txt $ python -m pip install -e . -.. code-block:: sh - - $ python -m pip install boto3 - Using Boto3 ~~~~~~~~~~~~~~ @@ -114,7 +126,6 @@ bandwidth to address them. Please use these community resources for getting help: * Ask a question on `Stack Overflow `__ and tag it with `boto3 `__ -* Come join the AWS Python community chat on `gitter `__ * Open a support ticket with `AWS Support `__ * If it turns out that you may have found a bug, please `open an issue `__ diff --git a/boto3/__init__.py b/boto3/__init__.py index 80830f6..13282d6 100644 --- a/boto3/__init__.py +++ b/boto3/__init__.py @@ -13,12 +13,11 @@ import logging -from boto3.session import Session from boto3.compat import _warn_deprecated_python - +from boto3.session import Session __author__ = 'Amazon Web Services' -__version__ = '1.20.34' +__version__ = '1.23.8' # The default Boto3 session; autoloaded when needed. diff --git a/boto3/compat.py b/boto3/compat.py index 7b617bb..24e3206 100644 --- a/boto3/compat.py +++ b/boto3/compat.py @@ -64,18 +64,16 @@ def filter_python_deprecation_warnings(): def _warn_deprecated_python(): - """Python 2.7 is deprecated so this code will no longer run. - - Use this template for future deprecation campaigns as needed. - """ - py_27_params = { - 'date': 'July 15, 2021', - 'blog_link': 'https://aws.amazon.com/blogs/developer/announcing-end-' - 'of-support-for-python-2-7-in-aws-sdk-for-python-and-' - 'aws-cli-v1/' + """Use this template for future deprecation campaigns as needed.""" + py_36_params = { + 'date': 'May 30, 2022', + 'blog_link': ( + 'https://aws.amazon.com/blogs/developer/' + 'python-support-policy-updates-for-aws-sdks-and-tools/' + ) } deprecated_versions = { - (2, 7): py_27_params, + (3, 6): py_36_params, } py_version = sys.version_info[:2] @@ -84,7 +82,7 @@ def _warn_deprecated_python(): warning = ( "Boto3 will no longer support Python {}.{} " "starting {}. To continue receiving service updates, " - "bug fixes, and security updates please upgrade to Python 3.6 or " + "bug fixes, and security updates please upgrade to Python 3.7 or " "later. More information can be found here: {}" ).format(py_version[0], py_version[1], params['date'], params['blog_link']) warnings.warn(warning, PythonDeprecationWarning) diff --git a/boto3/docs/__init__.py b/boto3/docs/__init__.py index 7f3a0da..d1cd8b5 100644 --- a/boto3/docs/__init__.py +++ b/boto3/docs/__init__.py @@ -34,6 +34,7 @@ def generate_docs(root_dir, session): for service_name in session.get_available_services(): docs = ServiceDocumenter(service_name, session).document_service() service_doc_path = os.path.join( - services_doc_path, service_name + '.rst') + services_doc_path, service_name + '.rst' + ) with open(service_doc_path, 'wb') as f: f.write(docs) diff --git a/boto3/docs/action.py b/boto3/docs/action.py index be30ae5..907f428 100644 --- a/boto3/docs/action.py +++ b/boto3/docs/action.py @@ -11,16 +11,20 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from botocore import xform_name +from botocore.docs.method import ( + document_custom_method, + document_model_driven_method, +) from botocore.model import OperationModel from botocore.utils import get_service_module_name -from botocore.docs.method import document_model_driven_method -from botocore.docs.method import document_custom_method from boto3.docs.base import BaseDocumenter from boto3.docs.method import document_model_driven_resource_method -from boto3.docs.utils import get_resource_ignore_params -from boto3.docs.utils import get_resource_public_actions -from boto3.docs.utils import add_resource_type_overview +from boto3.docs.utils import ( + add_resource_type_overview, + get_resource_ignore_params, + get_resource_public_actions, +) class ActionDocumenter(BaseDocumenter): @@ -30,7 +34,8 @@ class ActionDocumenter(BaseDocumenter): for modeled_action in modeled_actions_list: modeled_actions[modeled_action.name] = modeled_action resource_actions = get_resource_public_actions( - self._resource.__class__) + self._resource.__class__ + ) self.member_map['actions'] = sorted(resource_actions) add_resource_type_overview( section=section, @@ -38,8 +43,10 @@ class ActionDocumenter(BaseDocumenter): description=( 'Actions call operations on resources. They may ' 'automatically handle the passing in of arguments set ' - 'from identifiers and some attributes.'), - intro_link='actions_intro') + 'from identifiers and some attributes.' + ), + intro_link='actions_intro', + ) for action_name in sorted(resource_actions): action_section = section.add_new_section(action_name) @@ -50,7 +57,7 @@ class ActionDocumenter(BaseDocumenter): resource_name=self._resource_name, event_emitter=self._resource.meta.client.meta.events, load_model=self._resource_model.load, - service_model=self._service_model + service_model=self._service_model, ) elif action_name in modeled_actions: document_action( @@ -62,11 +69,18 @@ class ActionDocumenter(BaseDocumenter): ) else: document_custom_method( - action_section, action_name, resource_actions[action_name]) + action_section, action_name, resource_actions[action_name] + ) -def document_action(section, resource_name, event_emitter, action_model, - service_model, include_signature=True): +def document_action( + section, + resource_name, + event_emitter, + action_model, + service_model, + include_signature=True, +): """Documents a resource action :param section: The section to write to @@ -83,7 +97,8 @@ def document_action(section, resource_name, event_emitter, action_model, It is useful for generating docstrings. """ operation_model = service_model.operation_model( - action_model.request.operation) + action_model.request.operation + ) ignore_params = get_resource_ignore_params(action_model.request.params) example_return_value = 'response' @@ -92,23 +107,31 @@ def document_action(section, resource_name, event_emitter, action_model, example_resource_name = xform_name(resource_name) if service_model.service_name == resource_name: example_resource_name = resource_name - example_prefix = '%s = %s.%s' % ( - example_return_value, example_resource_name, action_model.name) + example_prefix = '{} = {}.{}'.format( + example_return_value, example_resource_name, action_model.name + ) document_model_driven_resource_method( - section=section, method_name=action_model.name, + section=section, + method_name=action_model.name, operation_model=operation_model, event_emitter=event_emitter, method_description=operation_model.documentation, example_prefix=example_prefix, exclude_input=ignore_params, resource_action_model=action_model, - include_signature=include_signature + include_signature=include_signature, ) -def document_load_reload_action(section, action_name, resource_name, - event_emitter, load_model, service_model, - include_signature=True): +def document_load_reload_action( + section, + action_name, + resource_name, + event_emitter, + load_model, + service_model, + include_signature=True, +): """Documents the resource load action :param section: The section to write to @@ -127,22 +150,24 @@ def document_load_reload_action(section, action_name, resource_name, It is useful for generating docstrings. """ description = ( - 'Calls :py:meth:`%s.Client.%s` to update the attributes of the' - ' %s resource. Note that the load and reload methods are ' - 'the same method and can be used interchangeably.' % ( + 'Calls :py:meth:`{}.Client.{}` to update the attributes of the ' + '{} resource. Note that the load and reload methods are ' + 'the same method and can be used interchangeably.'.format( get_service_module_name(service_model), xform_name(load_model.request.operation), - resource_name) + resource_name, + ) ) example_resource_name = xform_name(resource_name) if service_model.service_name == resource_name: example_resource_name = resource_name - example_prefix = '%s.%s' % (example_resource_name, action_name) + example_prefix = f'{example_resource_name}.{action_name}' document_model_driven_method( - section=section, method_name=action_name, + section=section, + method_name=action_name, operation_model=OperationModel({}, service_model), event_emitter=event_emitter, method_description=description, example_prefix=example_prefix, - include_signature=include_signature + include_signature=include_signature, ) diff --git a/boto3/docs/attr.py b/boto3/docs/attr.py index 86e5e04..ced8bc2 100644 --- a/boto3/docs/attr.py +++ b/boto3/docs/attr.py @@ -19,8 +19,15 @@ class ResourceShapeDocumenter(ResponseParamsDocumenter): EVENT_NAME = 'resource-shape' -def document_attribute(section, service_name, resource_name, attr_name, - event_emitter, attr_model, include_signature=True): +def document_attribute( + section, + service_name, + resource_name, + attr_name, + event_emitter, + attr_model, + include_signature=True, +): if include_signature: section.style.start_sphinx_py_attr(attr_name) # Note that an attribute may have one, may have many, or may have no @@ -28,27 +35,28 @@ def document_attribute(section, service_name, resource_name, attr_name, # operation_name to the resource name if we ever to hook in and modify # a particular attribute. ResourceShapeDocumenter( - service_name=service_name, operation_name=resource_name, - event_emitter=event_emitter).document_params( - section=section, - shape=attr_model) + service_name=service_name, + operation_name=resource_name, + event_emitter=event_emitter, + ).document_params(section=section, shape=attr_model) -def document_identifier(section, resource_name, identifier_model, - include_signature=True): +def document_identifier( + section, resource_name, identifier_model, include_signature=True +): if include_signature: section.style.start_sphinx_py_attr(identifier_model.name) description = get_identifier_description( - resource_name, identifier_model.name) - description = '*(string)* ' + description - section.write(description) + resource_name, identifier_model.name + ) + section.write(f'*(string)* {description}') def document_reference(section, reference_model, include_signature=True): if include_signature: section.style.start_sphinx_py_attr(reference_model.name) - reference_type = '(:py:class:`%s`) ' % reference_model.resource.type + reference_type = f'(:py:class:`{reference_model.resource.type}`) ' section.write(reference_type) section.include_doc_string( - 'The related %s if set, otherwise ``None``.' % reference_model.name + f'The related {reference_model.name} if set, otherwise ``None``.' ) diff --git a/boto3/docs/base.py b/boto3/docs/base.py index 3daec8f..a12d945 100644 --- a/boto3/docs/base.py +++ b/boto3/docs/base.py @@ -13,7 +13,7 @@ from botocore.compat import OrderedDict -class BaseDocumenter(object): +class BaseDocumenter: def __init__(self, resource): self._resource = resource self._client = self._resource.meta.client @@ -24,8 +24,9 @@ class BaseDocumenter(object): self._service_docs_name = self._client.__class__.__name__ self.member_map = OrderedDict() self.represents_service_resource = ( - self._service_name == self._resource_name) + self._service_name == self._resource_name + ) @property def class_name(self): - return '%s.%s' % (self._service_docs_name, self._resource_name) + return f'{self._service_docs_name}.{self._resource_name}' diff --git a/boto3/docs/client.py b/boto3/docs/client.py index 38cfe9a..51e92e3 100644 --- a/boto3/docs/client.py +++ b/boto3/docs/client.py @@ -22,6 +22,7 @@ class Boto3ClientDocumenter(ClientDocumenter): section.style.new_line() section.write( 'client = boto3.client(\'{service}\')'.format( - service=self._service_name) + service=self._service_name + ) ) section.style.end_codeblock() diff --git a/boto3/docs/collection.py b/boto3/docs/collection.py index db0fb79..a1f9ca7 100644 --- a/boto3/docs/collection.py +++ b/boto3/docs/collection.py @@ -15,9 +15,11 @@ from botocore.docs.method import get_instance_public_methods from botocore.docs.utils import DocumentedShape from boto3.docs.base import BaseDocumenter -from boto3.docs.utils import get_resource_ignore_params from boto3.docs.method import document_model_driven_resource_method -from boto3.docs.utils import add_resource_type_overview +from boto3.docs.utils import ( + add_resource_type_overview, + get_resource_ignore_params, +) class CollectionDocumenter(BaseDocumenter): @@ -29,8 +31,10 @@ class CollectionDocumenter(BaseDocumenter): resource_type='Collections', description=( 'Collections provide an interface to iterate over and ' - 'manipulate groups of resources. '), - intro_link='guide_collections') + 'manipulate groups of resources. ' + ), + intro_link='guide_collections', + ) self.member_map['collections'] = collections_list for collection in collections: collection_section = section.add_new_section(collection.name) @@ -39,7 +43,8 @@ class CollectionDocumenter(BaseDocumenter): def _document_collection(self, section, collection): methods = get_instance_public_methods( - getattr(self._resource, collection.name)) + getattr(self._resource, collection.name) + ) document_collection_object(section, collection) batch_actions = {} for batch_action in collection.batch_actions: @@ -54,7 +59,7 @@ class CollectionDocumenter(BaseDocumenter): event_emitter=self._resource.meta.client.meta.events, batch_action_model=batch_actions[method], collection_model=collection, - service_model=self._resource.meta.client.meta.service_model + service_model=self._resource.meta.client.meta.service_model, ) else: document_collection_method( @@ -63,12 +68,13 @@ class CollectionDocumenter(BaseDocumenter): action_name=method, event_emitter=self._resource.meta.client.meta.events, collection_model=collection, - service_model=self._resource.meta.client.meta.service_model + service_model=self._resource.meta.client.meta.service_model, ) -def document_collection_object(section, collection_model, - include_signature=True): +def document_collection_object( + section, collection_model, include_signature=True +): """Documents a collection resource object :param section: The section to write to @@ -81,16 +87,24 @@ def document_collection_object(section, collection_model, if include_signature: section.style.start_sphinx_py_attr(collection_model.name) section.include_doc_string( - 'A collection of %s resources.' % collection_model.resource.type) + f'A collection of {collection_model.resource.type} resources.' + ) section.include_doc_string( - 'A %s Collection will include all resources by default, ' - 'and extreme caution should be taken when performing ' - 'actions on all resources.' % collection_model.resource.type) + f'A {collection_model.resource.type} Collection will include all ' + f'resources by default, and extreme caution should be taken when ' + f'performing actions on all resources.' + ) -def document_batch_action(section, resource_name, event_emitter, - batch_action_model, service_model, collection_model, - include_signature=True): +def document_batch_action( + section, + resource_name, + event_emitter, + batch_action_model, + service_model, + collection_model, + include_signature=True, +): """Documents a collection's batch action :param section: The section to write to @@ -112,9 +126,11 @@ def document_batch_action(section, resource_name, event_emitter, It is useful for generating docstrings. """ operation_model = service_model.operation_model( - batch_action_model.request.operation) + batch_action_model.request.operation + ) ignore_params = get_resource_ignore_params( - batch_action_model.request.params) + batch_action_model.request.params + ) example_return_value = 'response' if batch_action_model.resource: @@ -123,25 +139,34 @@ def document_batch_action(section, resource_name, event_emitter, example_resource_name = xform_name(resource_name) if service_model.service_name == resource_name: example_resource_name = resource_name - example_prefix = '%s = %s.%s.%s' % ( - example_return_value, example_resource_name, - collection_model.name, batch_action_model.name + example_prefix = '{} = {}.{}.{}'.format( + example_return_value, + example_resource_name, + collection_model.name, + batch_action_model.name, ) document_model_driven_resource_method( - section=section, method_name=batch_action_model.name, + section=section, + method_name=batch_action_model.name, operation_model=operation_model, event_emitter=event_emitter, method_description=operation_model.documentation, example_prefix=example_prefix, exclude_input=ignore_params, resource_action_model=batch_action_model, - include_signature=include_signature + include_signature=include_signature, ) -def document_collection_method(section, resource_name, action_name, - event_emitter, collection_model, service_model, - include_signature=True): +def document_collection_method( + section, + resource_name, + action_name, + event_emitter, + collection_model, + service_model, + include_signature=True, +): """Documents a collection method :param section: The section to write to @@ -161,7 +186,8 @@ def document_collection_method(section, resource_name, action_name, It is useful for generating docstrings. """ operation_model = service_model.operation_model( - collection_model.request.operation) + collection_model.request.operation + ) underlying_operation_members = [] if operation_model.input_shape: @@ -174,71 +200,87 @@ def document_collection_method(section, resource_name, action_name, custom_action_info_dict = { 'all': { 'method_description': ( - 'Creates an iterable of all %s resources ' - 'in the collection.' % collection_model.resource.type), - 'example_prefix': '%s_iterator = %s.%s.all' % ( + f'Creates an iterable of all {collection_model.resource.type} ' + f'resources in the collection.' + ), + 'example_prefix': '{}_iterator = {}.{}.all'.format( xform_name(collection_model.resource.type), - example_resource_name, collection_model.name), - 'exclude_input': underlying_operation_members + example_resource_name, + collection_model.name, + ), + 'exclude_input': underlying_operation_members, }, 'filter': { 'method_description': ( - 'Creates an iterable of all %s resources ' - 'in the collection filtered by kwargs passed to ' - 'method. A %s collection will include all resources by ' - 'default if no filters are provided, and extreme ' - 'caution should be taken when performing actions ' - 'on all resources.' % ( - collection_model.resource.type, - collection_model.resource.type - )), - 'example_prefix': '%s_iterator = %s.%s.filter' % ( + f'Creates an iterable of all {collection_model.resource.type} ' + f'resources in the collection filtered by kwargs passed to ' + f'method. A {collection_model.resource.type} collection will ' + f'include all resources by default if no filters are provided, ' + f'and extreme caution should be taken when performing actions ' + f'on all resources.' + ), + 'example_prefix': '{}_iterator = {}.{}.filter'.format( xform_name(collection_model.resource.type), - example_resource_name, collection_model.name), + example_resource_name, + collection_model.name, + ), 'exclude_input': get_resource_ignore_params( - collection_model.request.params) + collection_model.request.params + ), }, 'limit': { 'method_description': ( - 'Creates an iterable up to a specified amount of ' - '%s resources in the collection.' % - collection_model.resource.type), - 'example_prefix': '%s_iterator = %s.%s.limit' % ( + f'Creates an iterable up to a specified amount of ' + f'{collection_model.resource.type} resources in the collection.' + ), + 'example_prefix': '{}_iterator = {}.{}.limit'.format( xform_name(collection_model.resource.type), - example_resource_name, collection_model.name), + example_resource_name, + collection_model.name, + ), 'include_input': [ DocumentedShape( - name='count', type_name='integer', + name='count', + type_name='integer', documentation=( 'The limit to the number of resources ' - 'in the iterable.'))], - 'exclude_input': underlying_operation_members + 'in the iterable.' + ), + ) + ], + 'exclude_input': underlying_operation_members, }, 'page_size': { 'method_description': ( - 'Creates an iterable of all %s resources ' - 'in the collection, but limits the number of ' - 'items returned by each service call by the specified ' - 'amount.' % collection_model.resource.type), - 'example_prefix': '%s_iterator = %s.%s.page_size' % ( + f'Creates an iterable of all {collection_model.resource.type} ' + f'resources in the collection, but limits the number of ' + f'items returned by each service call by the specified amount.' + ), + 'example_prefix': '{}_iterator = {}.{}.page_size'.format( xform_name(collection_model.resource.type), - example_resource_name, collection_model.name), + example_resource_name, + collection_model.name, + ), 'include_input': [ DocumentedShape( - name='count', type_name='integer', + name='count', + type_name='integer', documentation=( - 'The number of items returned by each ' - 'service call'))], - 'exclude_input': underlying_operation_members - } + 'The number of items returned by each ' 'service call' + ), + ) + ], + 'exclude_input': underlying_operation_members, + }, } if action_name in custom_action_info_dict: action_info = custom_action_info_dict[action_name] document_model_driven_resource_method( - section=section, method_name=action_name, + section=section, + method_name=action_name, operation_model=operation_model, event_emitter=event_emitter, resource_action_model=collection_model, include_signature=include_signature, - **action_info + **action_info, ) diff --git a/boto3/docs/docstring.py b/boto3/docs/docstring.py index f2d6388..daf6787 100644 --- a/boto3/docs/docstring.py +++ b/boto3/docs/docstring.py @@ -12,15 +12,18 @@ # language governing permissions and limitations under the License. from botocore.docs.docstring import LazyLoadedDocstring -from boto3.docs.action import document_action -from boto3.docs.action import document_load_reload_action +from boto3.docs.action import document_action, document_load_reload_action +from boto3.docs.attr import ( + document_attribute, + document_identifier, + document_reference, +) +from boto3.docs.collection import ( + document_batch_action, + document_collection_method, + document_collection_object, +) from boto3.docs.subresource import document_sub_resource -from boto3.docs.attr import document_attribute -from boto3.docs.attr import document_identifier -from boto3.docs.attr import document_reference -from boto3.docs.collection import document_collection_object -from boto3.docs.collection import document_collection_method -from boto3.docs.collection import document_batch_action from boto3.docs.waiter import document_resource_waiter diff --git a/boto3/docs/method.py b/boto3/docs/method.py index 40b5650..b752008 100644 --- a/boto3/docs/method.py +++ b/boto3/docs/method.py @@ -14,14 +14,24 @@ from botocore.docs.method import document_model_driven_method def document_model_driven_resource_method( - section, method_name, operation_model, event_emitter, - method_description=None, example_prefix=None, include_input=None, - include_output=None, exclude_input=None, exclude_output=None, - document_output=True, resource_action_model=None, - include_signature=True): + section, + method_name, + operation_model, + event_emitter, + method_description=None, + example_prefix=None, + include_input=None, + include_output=None, + exclude_input=None, + exclude_output=None, + document_output=True, + resource_action_model=None, + include_signature=True, +): document_model_driven_method( - section=section, method_name=method_name, + section=section, + method_name=method_name, operation_model=operation_model, event_emitter=event_emitter, method_description=method_description, @@ -31,7 +41,7 @@ def document_model_driven_resource_method( exclude_input=exclude_input, exclude_output=exclude_output, document_output=document_output, - include_signature=include_signature + include_signature=include_signature, ) # If this action returns a resource modify the return example to @@ -42,24 +52,21 @@ def document_model_driven_resource_method( resource_type = resource_action_model.resource.type new_return_section = section.add_new_section('return') - return_resource_type = '%s.%s' % ( - operation_model.service_model.service_name, - resource_type) + return_resource_type = '{}.{}'.format( + operation_model.service_model.service_name, resource_type + ) - return_type = ':py:class:`%s`' % return_resource_type - return_description = '%s resource' % (resource_type) + return_type = f':py:class:`{return_resource_type}`' + return_description = f'{resource_type} resource' if _method_returns_resource_list(resource_action_model.resource): - return_type = 'list(%s)' % return_type - return_description = 'A list of %s resources' % ( - resource_type) + return_type = f'list({return_type})' + return_description = f'A list of {resource_type} resources' new_return_section.style.new_line() - new_return_section.write( - ':rtype: %s' % return_type) + new_return_section.write(f':rtype: {return_type}') new_return_section.style.new_line() - new_return_section.write( - ':returns: %s' % return_description) + new_return_section.write(f':returns: {return_description}') new_return_section.style.new_line() diff --git a/boto3/docs/resource.py b/boto3/docs/resource.py index 9ce4a7c..7d5516e 100644 --- a/boto3/docs/resource.py +++ b/boto3/docs/resource.py @@ -13,23 +13,27 @@ from botocore import xform_name from botocore.docs.utils import get_official_service_name -from boto3.docs.base import BaseDocumenter from boto3.docs.action import ActionDocumenter -from boto3.docs.waiter import WaiterResourceDocumenter +from boto3.docs.attr import ( + document_attribute, + document_identifier, + document_reference, +) +from boto3.docs.base import BaseDocumenter from boto3.docs.collection import CollectionDocumenter from boto3.docs.subresource import SubResourceDocumenter -from boto3.docs.attr import document_attribute -from boto3.docs.attr import document_identifier -from boto3.docs.attr import document_reference -from boto3.docs.utils import get_identifier_args_for_signature -from boto3.docs.utils import get_identifier_values_for_example -from boto3.docs.utils import get_identifier_description -from boto3.docs.utils import add_resource_type_overview +from boto3.docs.utils import ( + add_resource_type_overview, + get_identifier_args_for_signature, + get_identifier_description, + get_identifier_values_for_example, +) +from boto3.docs.waiter import WaiterResourceDocumenter class ResourceDocumenter(BaseDocumenter): def __init__(self, resource, botocore_session): - super(ResourceDocumenter, self).__init__(resource) + super().__init__(resource) self._botocore_session = botocore_session def document_resource(self, section): @@ -57,7 +61,8 @@ class ResourceDocumenter(BaseDocumenter): # Write out the class signature. class_args = get_identifier_args_for_signature(identifier_names) section.style.start_sphinx_py_class( - class_name='%s(%s)' % (self.class_name, class_args)) + class_name=f'{self.class_name}({class_args})' + ) # Add as short description about the resource description_section = section.add_new_section('description') @@ -73,11 +78,12 @@ class ResourceDocumenter(BaseDocumenter): self._add_params_description(param_section, identifier_names) def _add_description(self, section): - official_service_name = get_official_service_name( - self._service_model) + official_service_name = get_official_service_name(self._service_model) section.write( - 'A resource representing an %s %s' % ( - official_service_name, self._resource_name)) + 'A resource representing an {} {}'.format( + official_service_name, self._resource_name + ) + ) def _add_example(self, section, identifier_names): section.style.start_codeblock() @@ -86,39 +92,49 @@ class ResourceDocumenter(BaseDocumenter): section.style.new_line() section.style.new_line() section.write( - '%s = boto3.resource(\'%s\')' % ( - self._service_name, self._service_name) + '{} = boto3.resource(\'{}\')'.format( + self._service_name, self._service_name + ) ) section.style.new_line() example_values = get_identifier_values_for_example(identifier_names) section.write( - '%s = %s.%s(%s)' % ( - xform_name(self._resource_name), self._service_name, - self._resource_name, example_values)) + '{} = {}.{}({})'.format( + xform_name(self._resource_name), + self._service_name, + self._resource_name, + example_values, + ) + ) section.style.end_codeblock() def _add_params_description(self, section, identifier_names): for identifier_name in identifier_names: description = get_identifier_description( - self._resource_name, identifier_name) - section.write(':type %s: string' % identifier_name) + self._resource_name, identifier_name + ) + section.write(f':type {identifier_name}: string') section.style.new_line() - section.write(':param %s: %s' % ( - identifier_name, description)) + section.write(f':param {identifier_name}: {description}') section.style.new_line() def _add_overview_of_members(self, section): for resource_member_type in self.member_map: section.style.new_line() - section.write('These are the resource\'s available %s:' % ( - resource_member_type)) + section.write( + f'These are the resource\'s available {resource_member_type}:' + ) section.style.new_line() for member in self.member_map[resource_member_type]: - if resource_member_type in ['identifiers', 'attributes', - 'references', 'collections']: - section.style.li(':py:attr:`%s`' % member) + if resource_member_type in ( + 'attributes', + 'collections', + 'identifiers', + 'references', + ): + section.style.li(f':py:attr:`{member}`') else: - section.style.li(':py:meth:`%s()`' % member) + section.style.li(f':py:meth:`{member}()`') def _add_identifiers(self, section): identifiers = self._resource.meta.resource_model.identifiers @@ -131,15 +147,17 @@ class ResourceDocumenter(BaseDocumenter): resource_type='Identifiers', description=( 'Identifiers are properties of a resource that are ' - 'set upon instantiation of the resource.'), - intro_link='identifiers_attributes_intro') + 'set upon instantiation of the resource.' + ), + intro_link='identifiers_attributes_intro', + ) for identifier in identifiers: identifier_section = section.add_new_section(identifier.name) member_list.append(identifier.name) document_identifier( section=identifier_section, resource_name=self._resource_name, - identifier_model=identifier + identifier_model=identifier, ) def _add_attributes(self, section): @@ -147,9 +165,11 @@ class ResourceDocumenter(BaseDocumenter): attributes = {} if self._resource.meta.resource_model.shape: shape = service_model.shape_for( - self._resource.meta.resource_model.shape) + self._resource.meta.resource_model.shape + ) attributes = self._resource.meta.resource_model.get_attributes( - shape) + shape + ) section = section.add_new_section('attributes') attribute_list = [] if attributes: @@ -160,8 +180,10 @@ class ResourceDocumenter(BaseDocumenter): 'Attributes provide access' ' to the properties of a resource. Attributes are lazy-' 'loaded the first time one is accessed via the' - ' :py:meth:`load` method.'), - intro_link='identifiers_attributes_intro') + ' :py:meth:`load` method.' + ), + intro_link='identifiers_attributes_intro', + ) self.member_map['attributes'] = attribute_list for attr_name in sorted(attributes): _, attr_shape = attributes[attr_name] @@ -173,7 +195,7 @@ class ResourceDocumenter(BaseDocumenter): resource_name=self._resource_name, attr_name=attr_name, event_emitter=self._resource.meta.client.meta.events, - attr_model=attr_shape + attr_model=attr_shape, ) def _add_references(self, section): @@ -186,15 +208,16 @@ class ResourceDocumenter(BaseDocumenter): resource_type='References', description=( 'References are related resource instances that have ' - 'a belongs-to relationship.'), - intro_link='references_intro') + 'a belongs-to relationship.' + ), + intro_link='references_intro', + ) self.member_map['references'] = reference_list for reference in references: reference_section = section.add_new_section(reference.name) reference_list.append(reference.name) document_reference( - section=reference_section, - reference_model=reference + section=reference_section, reference_model=reference ) def _add_actions(self, section): @@ -226,9 +249,11 @@ class ResourceDocumenter(BaseDocumenter): waiters = self._resource.meta.resource_model.waiters if waiters: service_waiter_model = self._botocore_session.get_waiter_model( - self._service_name) + self._service_name + ) documenter = WaiterResourceDocumenter( - self._resource, service_waiter_model) + self._resource, service_waiter_model + ) documenter.member_map = self.member_map documenter.document_resource_waiters(section) @@ -236,16 +261,14 @@ class ResourceDocumenter(BaseDocumenter): class ServiceResourceDocumenter(ResourceDocumenter): @property def class_name(self): - return '%s.ServiceResource' % self._service_docs_name + return f'{self._service_docs_name}.ServiceResource' def _add_title(self, section): section.style.h2('Service Resource') def _add_description(self, section): - official_service_name = get_official_service_name( - self._service_model) - section.write( - 'A resource representing %s' % official_service_name) + official_service_name = get_official_service_name(self._service_model) + section.write(f'A resource representing {official_service_name}') def _add_example(self, section, identifier_names): section.style.start_codeblock() @@ -254,6 +277,6 @@ class ServiceResourceDocumenter(ResourceDocumenter): section.style.new_line() section.style.new_line() section.write( - '%s = boto3.resource(\'%s\')' % ( - self._service_name, self._service_name)) + f'{self._service_name} = boto3.resource(\'{self._service_name}\')' + ) section.style.end_codeblock() diff --git a/boto3/docs/service.py b/boto3/docs/service.py index 602b8c9..56a4e63 100644 --- a/boto3/docs/service.py +++ b/boto3/docs/service.py @@ -12,15 +12,14 @@ # language governing permissions and limitations under the License. import os -import boto3 -from botocore.exceptions import DataNotFoundError -from botocore.docs.service import ServiceDocumenter as BaseServiceDocumenter from botocore.docs.bcdoc.restdoc import DocumentStructure +from botocore.docs.service import ServiceDocumenter as BaseServiceDocumenter +from botocore.exceptions import DataNotFoundError -from boto3.utils import ServiceContext +import boto3 from boto3.docs.client import Boto3ClientDocumenter -from boto3.docs.resource import ResourceDocumenter -from boto3.docs.resource import ServiceResourceDocumenter +from boto3.docs.resource import ResourceDocumenter, ServiceResourceDocumenter +from boto3.utils import ServiceContext class ServiceDocumenter(BaseServiceDocumenter): @@ -28,7 +27,7 @@ class ServiceDocumenter(BaseServiceDocumenter): EXAMPLE_PATH = os.path.join(os.path.dirname(boto3.__file__), 'examples') def __init__(self, service_name, session): - super(ServiceDocumenter, self).__init__( + super().__init__( service_name=service_name, # I know that this is an internal attribute, but the botocore session # is needed to load the paginator and waiter models. @@ -47,7 +46,7 @@ class ServiceDocumenter(BaseServiceDocumenter): 'waiters', 'service-resource', 'resources', - 'examples' + 'examples', ] def document_service(self): @@ -56,8 +55,8 @@ class ServiceDocumenter(BaseServiceDocumenter): :returns: The reStructured text of the documented service. """ doc_structure = DocumentStructure( - self._service_name, section_names=self.sections, - target='html') + self._service_name, section_names=self.sections, target='html' + ) self.title(doc_structure.get_section('title')) self.table_of_contents(doc_structure.get_section('table-of-contents')) @@ -66,7 +65,8 @@ class ServiceDocumenter(BaseServiceDocumenter): self.waiter_api(doc_structure.get_section('waiters')) if self._service_resource: self._document_service_resource( - doc_structure.get_section('service-resource')) + doc_structure.get_section('service-resource') + ) self._document_resources(doc_structure.get_section('resources')) self._document_examples(doc_structure.get_section('examples')) return doc_structure.flush_structure() @@ -82,42 +82,45 @@ class ServiceDocumenter(BaseServiceDocumenter): def _document_service_resource(self, section): ServiceResourceDocumenter( - self._service_resource, self._session).document_resource( - section) + self._service_resource, self._session + ).document_resource(section) def _document_resources(self, section): temp_identifier_value = 'foo' loader = self._session.get_component('data_loader') json_resource_model = loader.load_service_model( - self._service_name, 'resources-1') + self._service_name, 'resources-1' + ) service_model = self._service_resource.meta.client.meta.service_model for resource_name in json_resource_model['resources']: resource_model = json_resource_model['resources'][resource_name] - resource_cls = self._boto3_session.resource_factory.\ - load_from_definition( + resource_cls = ( + self._boto3_session.resource_factory.load_from_definition( resource_name=resource_name, single_resource_json_definition=resource_model, service_context=ServiceContext( service_name=self._service_name, resource_json_definitions=json_resource_model[ - 'resources'], + 'resources' + ], service_model=service_model, - service_waiter_model=None - ) + service_waiter_model=None, + ), ) + ) identifiers = resource_cls.meta.resource_model.identifiers args = [] for _ in identifiers: args.append(temp_identifier_value) resource = resource_cls(*args, client=self._client) - ResourceDocumenter( - resource, self._session).document_resource( - section.add_new_section(resource.meta.resource_model.name)) + ResourceDocumenter(resource, self._session).document_resource( + section.add_new_section(resource.meta.resource_model.name) + ) def _get_example_file(self): return os.path.realpath( - os.path.join(self.EXAMPLE_PATH, - self._service_name + '.rst')) + os.path.join(self.EXAMPLE_PATH, self._service_name + '.rst') + ) def _document_examples(self, section): examples_file = self._get_example_file() @@ -127,5 +130,5 @@ class ServiceDocumenter(BaseServiceDocumenter): section.write(".. contents::\n :local:\n :depth: 1") section.style.new_line() section.style.new_line() - with open(examples_file, 'r') as f: + with open(examples_file) as f: section.write(f.read()) diff --git a/boto3/docs/subresource.py b/boto3/docs/subresource.py index d0685fa..e22311c 100644 --- a/boto3/docs/subresource.py +++ b/boto3/docs/subresource.py @@ -14,10 +14,12 @@ from botocore import xform_name from botocore.utils import get_service_module_name from boto3.docs.base import BaseDocumenter -from boto3.docs.utils import get_identifier_args_for_signature -from boto3.docs.utils import get_identifier_values_for_example -from boto3.docs.utils import get_identifier_description -from boto3.docs.utils import add_resource_type_overview +from boto3.docs.utils import ( + add_resource_type_overview, + get_identifier_args_for_signature, + get_identifier_description, + get_identifier_values_for_example, +) class SubResourceDocumenter(BaseDocumenter): @@ -28,11 +30,13 @@ class SubResourceDocumenter(BaseDocumenter): description=( 'Sub-resources are methods that create a new instance of a' ' child resource. This resource\'s identifiers get passed' - ' along to the child.'), - intro_link='subresources_intro') + ' along to the child.' + ), + intro_link='subresources_intro', + ) sub_resources = sorted( self._resource.meta.resource_model.subresources, - key=lambda sub_resource: sub_resource.name + key=lambda sub_resource: sub_resource.name, ) sub_resources_list = [] self.member_map['sub-resources'] = sub_resources_list @@ -43,12 +47,17 @@ class SubResourceDocumenter(BaseDocumenter): section=sub_resource_section, resource_name=self._resource_name, sub_resource_model=sub_resource, - service_model=self._service_model + service_model=self._service_model, ) -def document_sub_resource(section, resource_name, sub_resource_model, - service_model, include_signature=True): +def document_sub_resource( + section, + resource_name, + sub_resource_model, + service_model, + include_signature=True, +): """Documents a resource action :param section: The section to write to @@ -70,21 +79,22 @@ def document_sub_resource(section, resource_name, sub_resource_model, if include_signature: signature_args = get_identifier_args_for_signature(identifiers_needed) section.style.start_sphinx_py_method( - sub_resource_model.name, signature_args) + sub_resource_model.name, signature_args + ) - method_intro_section = section.add_new_section( - 'method-intro') - description = 'Creates a %s resource.' % sub_resource_model.resource.type + method_intro_section = section.add_new_section('method-intro') + description = f'Creates a {sub_resource_model.resource.type} resource.' method_intro_section.include_doc_string(description) example_section = section.add_new_section('example') example_values = get_identifier_values_for_example(identifiers_needed) example_resource_name = xform_name(resource_name) if service_model.service_name == resource_name: example_resource_name = resource_name - example = '%s = %s.%s(%s)' % ( + example = '{} = {}.{}({})'.format( xform_name(sub_resource_model.resource.type), example_resource_name, - sub_resource_model.name, example_values + sub_resource_model.name, + example_values, ) example_section.style.start_codeblock() example_section.write(example) @@ -93,20 +103,23 @@ def document_sub_resource(section, resource_name, sub_resource_model, param_section = section.add_new_section('params') for identifier in identifiers_needed: description = get_identifier_description( - sub_resource_model.name, identifier) - param_section.write(':type %s: string' % identifier) + sub_resource_model.name, identifier + ) + param_section.write(f':type {identifier}: string') param_section.style.new_line() - param_section.write(':param %s: %s' % ( - identifier, description)) + param_section.write(f':param {identifier}: {description}') param_section.style.new_line() return_section = section.add_new_section('return') return_section.style.new_line() return_section.write( - ':rtype: :py:class:`%s.%s`' % ( + ':rtype: :py:class:`{}.{}`'.format( get_service_module_name(service_model), - sub_resource_model.resource.type)) + sub_resource_model.resource.type, + ) + ) return_section.style.new_line() return_section.write( - ':returns: A %s resource' % sub_resource_model.resource.type) + f':returns: A {sub_resource_model.resource.type} resource' + ) return_section.style.new_line() diff --git a/boto3/docs/utils.py b/boto3/docs/utils.py index 60ad419..13a098d 100644 --- a/boto3/docs/utils.py +++ b/boto3/docs/utils.py @@ -14,8 +14,6 @@ import inspect import jmespath -from botocore.compat import six - def get_resource_ignore_params(params): """Helper method to determine which parameters to ignore for actions @@ -39,10 +37,7 @@ def get_resource_ignore_params(params): def is_resource_action(action_handle): - if six.PY3: - return inspect.isfunction(action_handle) - else: - return inspect.ismethod(action_handle) + return inspect.isfunction(action_handle) def get_resource_public_actions(resource_class): @@ -58,8 +53,7 @@ def get_resource_public_actions(resource_class): def get_identifier_values_for_example(identifier_names): - example_values = ['\'%s\'' % identifier for identifier in identifier_names] - return ','.join(example_values) + return ','.join([f'\'{identifier}\'' for identifier in identifier_names]) def get_identifier_args_for_signature(identifier_names): @@ -67,12 +61,15 @@ def get_identifier_args_for_signature(identifier_names): def get_identifier_description(resource_name, identifier_name): - return "The %s's %s identifier. This **must** be set." % ( - resource_name, identifier_name) + return ( + f"The {resource_name}'s {identifier_name} identifier. " + f"This **must** be set." + ) -def add_resource_type_overview(section, resource_type, description, - intro_link=None): +def add_resource_type_overview( + section, resource_type, description, intro_link=None +): section.style.new_line() section.write('.. rst-class:: admonition-title') section.style.new_line() @@ -83,22 +80,25 @@ def add_resource_type_overview(section, resource_type, description, section.write(description) section.style.new_line() if intro_link is not None: - section.write('For more information about %s refer to the ' - ':ref:`Resources Introduction Guide<%s>`.' % ( - resource_type.lower(), intro_link)) + section.write( + f'For more information about {resource_type.lower()} refer to the ' + f':ref:`Resources Introduction Guide<{intro_link}>`.' + ) section.style.new_line() -class DocumentModifiedShape(object): - def __init__(self, shape_name, new_type, new_description, - new_example_value): +class DocumentModifiedShape: + def __init__( + self, shape_name, new_type, new_description, new_example_value + ): self._shape_name = shape_name self._new_type = new_type self._new_description = new_description self._new_example_value = new_example_value - def replace_documentation_for_matching_shape(self, event_name, section, - **kwargs): + def replace_documentation_for_matching_shape( + self, event_name, section, **kwargs + ): if self._shape_name == section.context.get('shape'): self._replace_documentation(event_name, section) for section_name in section.available_sections: @@ -107,23 +107,31 @@ class DocumentModifiedShape(object): self._replace_documentation(event_name, sub_section) else: self.replace_documentation_for_matching_shape( - event_name, sub_section) + event_name, sub_section + ) def _replace_documentation(self, event_name, section): - if event_name.startswith('docs.request-example') or \ - event_name.startswith('docs.response-example'): + if event_name.startswith( + 'docs.request-example' + ) or event_name.startswith('docs.response-example'): section.remove_all_sections() section.clear_text() section.write(self._new_example_value) - if event_name.startswith('docs.request-params') or \ - event_name.startswith('docs.response-params'): + if event_name.startswith( + 'docs.request-params' + ) or event_name.startswith('docs.response-params'): + allowed_sections = ( + 'param-name', + 'param-documentation', + 'end-structure', + 'param-type', + 'end-param', + ) for section_name in section.available_sections: # Delete any extra members as a new shape is being # used. - if section_name not in ['param-name', 'param-documentation', - 'end-structure', 'param-type', - 'end-param']: + if section_name not in allowed_sections: section.delete_section(section_name) # Update the documentation @@ -135,8 +143,7 @@ class DocumentModifiedShape(object): type_section = section.get_section('param-type') if type_section.getvalue().decode('utf-8').startswith(':type'): type_section.clear_text() - type_section.write(':type %s: %s' % ( - section.name, self._new_type)) + type_section.write(f':type {section.name}: {self._new_type}') else: type_section.clear_text() - type_section.style.italics('(%s) -- ' % self._new_type) + type_section.style.italics(f'({self._new_type}) -- ') diff --git a/boto3/docs/waiter.py b/boto3/docs/waiter.py index 38da4d8..255d850 100644 --- a/boto3/docs/waiter.py +++ b/boto3/docs/waiter.py @@ -11,17 +11,19 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from botocore import xform_name -from botocore.utils import get_service_module_name from botocore.docs.method import document_model_driven_method +from botocore.utils import get_service_module_name from boto3.docs.base import BaseDocumenter -from boto3.docs.utils import get_resource_ignore_params -from boto3.docs.utils import add_resource_type_overview +from boto3.docs.utils import ( + add_resource_type_overview, + get_resource_ignore_params, +) class WaiterResourceDocumenter(BaseDocumenter): def __init__(self, resource, service_waiter_model): - super(WaiterResourceDocumenter, self).__init__(resource) + super().__init__(resource) self._service_waiter_model = service_waiter_model def document_resource_waiters(self, section): @@ -31,8 +33,10 @@ class WaiterResourceDocumenter(BaseDocumenter): resource_type='Waiters', description=( 'Waiters provide an interface to wait for a resource' - ' to reach a specific state.'), - intro_link='waiters_intro') + ' to reach a specific state.' + ), + intro_link='waiters_intro', + ) waiter_list = [] self.member_map['waiters'] = waiter_list for waiter in waiters: @@ -44,42 +48,54 @@ class WaiterResourceDocumenter(BaseDocumenter): event_emitter=self._resource.meta.client.meta.events, service_model=self._service_model, resource_waiter_model=waiter, - service_waiter_model=self._service_waiter_model + service_waiter_model=self._service_waiter_model, ) -def document_resource_waiter(section, resource_name, event_emitter, - service_model, resource_waiter_model, - service_waiter_model, include_signature=True): +def document_resource_waiter( + section, + resource_name, + event_emitter, + service_model, + resource_waiter_model, + service_waiter_model, + include_signature=True, +): waiter_model = service_waiter_model.get_waiter( - resource_waiter_model.waiter_name) - operation_model = service_model.operation_model( - waiter_model.operation) + resource_waiter_model.waiter_name + ) + operation_model = service_model.operation_model(waiter_model.operation) ignore_params = get_resource_ignore_params(resource_waiter_model.params) service_module_name = get_service_module_name(service_model) description = ( - 'Waits until this %s is %s. This method calls ' - ':py:meth:`%s.Waiter.%s.wait` which polls. ' - ':py:meth:`%s.Client.%s` every %s seconds until ' + 'Waits until this {} is {}. This method calls ' + ':py:meth:`{}.Waiter.{}.wait` which polls. ' + ':py:meth:`{}.Client.{}` every {} seconds until ' 'a successful state is reached. An error is returned ' - 'after %s failed checks.' % ( - resource_name, ' '.join(resource_waiter_model.name.split('_')[2:]), + 'after {} failed checks.'.format( + resource_name, + ' '.join(resource_waiter_model.name.split('_')[2:]), service_module_name, xform_name(resource_waiter_model.waiter_name), service_module_name, xform_name(waiter_model.operation), - waiter_model.delay, waiter_model.max_attempts)) - example_prefix = '%s.%s' % ( - xform_name(resource_name), resource_waiter_model.name) + waiter_model.delay, + waiter_model.max_attempts, + ) + ) + example_prefix = '{}.{}'.format( + xform_name(resource_name), resource_waiter_model.name + ) document_model_driven_method( - section=section, method_name=resource_waiter_model.name, + section=section, + method_name=resource_waiter_model.name, operation_model=operation_model, event_emitter=event_emitter, example_prefix=example_prefix, method_description=description, exclude_input=ignore_params, - include_signature=include_signature + include_signature=include_signature, ) if 'return' in section.available_sections: # Waiters do not return anything so we should remove diff --git a/boto3/dynamodb/conditions.py b/boto3/dynamodb/conditions.py index 41850b1..442f11c 100644 --- a/boto3/dynamodb/conditions.py +++ b/boto3/dynamodb/conditions.py @@ -10,18 +10,19 @@ # 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. -from collections import namedtuple import re +from collections import namedtuple -from boto3.exceptions import DynamoDBOperationNotSupportedError -from boto3.exceptions import DynamoDBNeedsConditionError -from boto3.exceptions import DynamoDBNeedsKeyConditionError - +from boto3.exceptions import ( + DynamoDBNeedsConditionError, + DynamoDBNeedsKeyConditionError, + DynamoDBOperationNotSupportedError, +) ATTR_NAME_REGEX = re.compile(r'[^.\[\]]+(?![^\[]*\])') -class ConditionBase(object): +class ConditionBase: expression_format = '' expression_operator = '' @@ -44,9 +45,11 @@ class ConditionBase(object): return Not(self) def get_expression(self): - return {'format': self.expression_format, - 'operator': self.expression_operator, - 'values': self._values} + return { + 'format': self.expression_format, + 'operator': self.expression_operator, + 'values': self._values, + } def __eq__(self, other): if isinstance(other, type(self)): @@ -58,7 +61,7 @@ class ConditionBase(object): return not self.__eq__(other) -class AttributeBase(object): +class AttributeBase: def __init__(self, name): self.name = name @@ -137,6 +140,7 @@ class ConditionAttributeBase(ConditionBase, AttributeBase): One example is the Size condition. To complete a condition, you need to apply another AttributeBase method like eq(). """ + def __init__(self, *values): ConditionBase.__init__(self, *values) # This is assuming the first value to the condition is the attribute @@ -144,8 +148,8 @@ class ConditionAttributeBase(ConditionBase, AttributeBase): AttributeBase.__init__(self, values[0].name) def __eq__(self, other): - return ( - ConditionBase.__eq__(self, other) and AttributeBase.__eq__(self, other) + return ConditionBase.__eq__(self, other) and AttributeBase.__eq__( + self, other ) def __ne__(self, other): @@ -241,6 +245,7 @@ class Key(AttributeBase): class Attr(AttributeBase): """Represents an DynamoDB item's attribute.""" + def ne(self, value): """Creates a condition where the attribute is not equal to the value @@ -289,13 +294,17 @@ class Attr(AttributeBase): BuiltConditionExpression = namedtuple( 'BuiltConditionExpression', - ['condition_expression', 'attribute_name_placeholders', - 'attribute_value_placeholders'] + [ + 'condition_expression', + 'attribute_name_placeholders', + 'attribute_value_placeholders', + ], ) -class ConditionExpressionBuilder(object): +class ConditionExpressionBuilder: """This class is used to build condition expressions with placeholders""" + def __init__(self): self._name_count = 0 self._value_count = 0 @@ -337,56 +346,79 @@ class ConditionExpressionBuilder(object): attribute_name_placeholders = {} attribute_value_placeholders = {} condition_expression = self._build_expression( - condition, attribute_name_placeholders, - attribute_value_placeholders, is_key_condition=is_key_condition) + condition, + attribute_name_placeholders, + attribute_value_placeholders, + is_key_condition=is_key_condition, + ) return BuiltConditionExpression( condition_expression=condition_expression, attribute_name_placeholders=attribute_name_placeholders, - attribute_value_placeholders=attribute_value_placeholders + attribute_value_placeholders=attribute_value_placeholders, ) - def _build_expression(self, condition, attribute_name_placeholders, - attribute_value_placeholders, is_key_condition): + def _build_expression( + self, + condition, + attribute_name_placeholders, + attribute_value_placeholders, + is_key_condition, + ): expression_dict = condition.get_expression() replaced_values = [] for value in expression_dict['values']: # Build the necessary placeholders for that value. # Placeholders are built for both attribute names and values. replaced_value = self._build_expression_component( - value, attribute_name_placeholders, - attribute_value_placeholders, condition.has_grouped_values, - is_key_condition) + value, + attribute_name_placeholders, + attribute_value_placeholders, + condition.has_grouped_values, + is_key_condition, + ) replaced_values.append(replaced_value) # Fill out the expression using the operator and the # values that have been replaced with placeholders. return expression_dict['format'].format( - *replaced_values, operator=expression_dict['operator']) + *replaced_values, operator=expression_dict['operator'] + ) - def _build_expression_component(self, value, attribute_name_placeholders, - attribute_value_placeholders, - has_grouped_values, is_key_condition): + def _build_expression_component( + self, + value, + attribute_name_placeholders, + attribute_value_placeholders, + has_grouped_values, + is_key_condition, + ): # Continue to recurse if the value is a ConditionBase in order # to extract out all parts of the expression. if isinstance(value, ConditionBase): return self._build_expression( - value, attribute_name_placeholders, - attribute_value_placeholders, is_key_condition) + value, + attribute_name_placeholders, + attribute_value_placeholders, + is_key_condition, + ) # If it is not a ConditionBase, we can recurse no further. # So we check if it is an attribute and add placeholders for # its name elif isinstance(value, AttributeBase): if is_key_condition and not isinstance(value, Key): raise DynamoDBNeedsKeyConditionError( - 'Attribute object %s is of type %s. ' - 'KeyConditionExpression only supports Attribute objects ' - 'of type Key' % (value.name, type(value))) + f'Attribute object {value.name} is of type {type(value)}. ' + f'KeyConditionExpression only supports Attribute objects ' + f'of type Key' + ) return self._build_name_placeholder( - value, attribute_name_placeholders) + value, attribute_name_placeholders + ) # If it is anything else, we treat it as a value and thus placeholders # are needed for the value. else: return self._build_value_placeholder( - value, attribute_value_placeholders, has_grouped_values) + value, attribute_value_placeholders, has_grouped_values + ) def _build_name_placeholder(self, value, attribute_name_placeholders): attribute_name = value.name @@ -405,8 +437,9 @@ class ConditionExpressionBuilder(object): # Replace the temporary placeholders with the designated placeholders. return placeholder_format % tuple(str_format_args) - def _build_value_placeholder(self, value, attribute_value_placeholders, - has_grouped_values=False): + def _build_value_placeholder( + self, value, attribute_value_placeholders, has_grouped_values=False + ): # If the values are grouped, we need to add a placeholder for # each element inside of the actual value. if has_grouped_values: diff --git a/boto3/dynamodb/table.py b/boto3/dynamodb/table.py index f2dd653..d96ec8f 100644 --- a/boto3/dynamodb/table.py +++ b/boto3/dynamodb/table.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import logging - logger = logging.getLogger(__name__) @@ -25,9 +24,9 @@ def register_table_methods(base_classes, **kwargs): # base class for every method we can just update this # class instead. Just be sure to move the bulk of the # actual method implementation to another class. -class TableResource(object): +class TableResource: def __init__(self, *args, **kwargs): - super(TableResource, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def batch_writer(self, overwrite_by_pkeys=None): """Create a batch writer object. @@ -56,14 +55,17 @@ class TableResource(object): ``["partition_key1", "sort_key2", "sort_key3"]`` """ - return BatchWriter(self.name, self.meta.client, - overwrite_by_pkeys=overwrite_by_pkeys) + return BatchWriter( + self.name, self.meta.client, overwrite_by_pkeys=overwrite_by_pkeys + ) -class BatchWriter(object): +class BatchWriter: """Automatically handle batch writes to DynamoDB for a single table.""" - def __init__(self, table_name, client, flush_amount=25, - overwrite_by_pkeys=None): + + def __init__( + self, table_name, client, flush_amount=25, overwrite_by_pkeys=None + ): """ :type table_name: str @@ -114,16 +116,22 @@ class BatchWriter(object): for item in self._items_buffer: if self._extract_pkey_values(item) == pkey_values_new: self._items_buffer.remove(item) - logger.debug("With overwrite_by_pkeys enabled, skipping " - "request:%s", item) + logger.debug( + "With overwrite_by_pkeys enabled, skipping " "request:%s", + item, + ) def _extract_pkey_values(self, request): if request.get('PutRequest'): - return [request['PutRequest']['Item'][key] - for key in self._overwrite_by_pkeys] + return [ + request['PutRequest']['Item'][key] + for key in self._overwrite_by_pkeys + ] elif request.get('DeleteRequest'): - return [request['DeleteRequest']['Key'][key] - for key in self._overwrite_by_pkeys] + return [ + request['DeleteRequest']['Key'][key] + for key in self._overwrite_by_pkeys + ] return None def _flush_if_needed(self): @@ -131,10 +139,11 @@ class BatchWriter(object): self._flush() def _flush(self): - items_to_send = self._items_buffer[:self._flush_amount] - self._items_buffer = self._items_buffer[self._flush_amount:] + items_to_send = self._items_buffer[: self._flush_amount] + self._items_buffer = self._items_buffer[self._flush_amount :] response = self._client.batch_write_item( - RequestItems={self._table_name: items_to_send}) + RequestItems={self._table_name: items_to_send} + ) unprocessed_items = response['UnprocessedItems'] if unprocessed_items and unprocessed_items[self._table_name]: @@ -143,8 +152,11 @@ class BatchWriter(object): self._items_buffer.extend(unprocessed_items[self._table_name]) else: self._items_buffer = [] - logger.debug("Batch write sent %s, unprocessed: %s", - len(items_to_send), len(self._items_buffer)) + logger.debug( + "Batch write sent %s, unprocessed: %s", + len(items_to_send), + len(self._items_buffer), + ) def __enter__(self): return self diff --git a/boto3/dynamodb/transform.py b/boto3/dynamodb/transform.py index cc94280..eef3aae 100644 --- a/boto3/dynamodb/transform.py +++ b/boto3/dynamodb/transform.py @@ -13,10 +13,9 @@ import copy from boto3.compat import collections_abc -from boto3.dynamodb.types import TypeSerializer, TypeDeserializer -from boto3.dynamodb.conditions import ConditionBase -from boto3.dynamodb.conditions import ConditionExpressionBuilder from boto3.docs.utils import DocumentModifiedShape +from boto3.dynamodb.conditions import ConditionBase, ConditionExpressionBuilder +from boto3.dynamodb.types import TypeDeserializer, TypeSerializer def register_high_level_interface(base_classes, **kwargs): @@ -27,16 +26,16 @@ def copy_dynamodb_params(params, **kwargs): return copy.deepcopy(params) -class DynamoDBHighLevelResource(object): +class DynamoDBHighLevelResource: def __init__(self, *args, **kwargs): - super(DynamoDBHighLevelResource, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Apply handler that creates a copy of the user provided dynamodb # item such that it can be modified. self.meta.client.meta.events.register( 'provide-client-params.dynamodb', copy_dynamodb_params, - unique_id='dynamodb-create-params-copy' + unique_id='dynamodb-create-params-copy', ) self._injector = TransformationInjector() @@ -45,21 +44,24 @@ class DynamoDBHighLevelResource(object): self.meta.client.meta.events.register( 'before-parameter-build.dynamodb', self._injector.inject_condition_expressions, - unique_id='dynamodb-condition-expression') + unique_id='dynamodb-condition-expression', + ) # Apply the handler that serializes the request from python # types to dynamodb types. self.meta.client.meta.events.register( 'before-parameter-build.dynamodb', self._injector.inject_attribute_value_input, - unique_id='dynamodb-attr-value-input') + unique_id='dynamodb-attr-value-input', + ) # Apply the handler that deserializes the response from dynamodb # types to python types. self.meta.client.meta.events.register( 'after-call.dynamodb', self._injector.inject_attribute_value_output, - unique_id='dynamodb-attr-value-output') + unique_id='dynamodb-attr-value-output', + ) # Apply the documentation customizations to account for # the transformations. @@ -73,7 +75,8 @@ class DynamoDBHighLevelResource(object): ), new_example_value=( '\'string\'|123|Binary(b\'bytes\')|True|None|set([\'string\'])' - '|set([123])|set([Binary(b\'bytes\')])|[]|{}') + '|set([123])|set([Binary(b\'bytes\')])|[]|{}' + ), ) key_expression_shape_docs = DocumentModifiedShape( @@ -87,7 +90,7 @@ class DynamoDBHighLevelResource(object): 'listed in the ' ':ref:`DynamoDB Reference Guide`.' ), - new_example_value='Key(\'mykey\').eq(\'myvalue\')' + new_example_value='Key(\'mykey\').eq(\'myvalue\')', ) con_expression_shape_docs = DocumentModifiedShape( @@ -101,29 +104,38 @@ class DynamoDBHighLevelResource(object): 'are listed in the ' ':ref:`DynamoDB Reference Guide`.' ), - new_example_value='Attr(\'myattribute\').eq(\'myvalue\')' + new_example_value='Attr(\'myattribute\').eq(\'myvalue\')', ) self.meta.client.meta.events.register( 'docs.*.dynamodb.*.complete-section', attr_value_shape_docs.replace_documentation_for_matching_shape, - unique_id='dynamodb-attr-value-docs') + unique_id='dynamodb-attr-value-docs', + ) self.meta.client.meta.events.register( 'docs.*.dynamodb.*.complete-section', key_expression_shape_docs.replace_documentation_for_matching_shape, - unique_id='dynamodb-key-expression-docs') + unique_id='dynamodb-key-expression-docs', + ) self.meta.client.meta.events.register( 'docs.*.dynamodb.*.complete-section', con_expression_shape_docs.replace_documentation_for_matching_shape, - unique_id='dynamodb-cond-expression-docs') + unique_id='dynamodb-cond-expression-docs', + ) -class TransformationInjector(object): +class TransformationInjector: """Injects the transformations into the user provided parameters.""" - def __init__(self, transformer=None, condition_builder=None, - serializer=None, deserializer=None): + + def __init__( + self, + transformer=None, + condition_builder=None, + serializer=None, + deserializer=None, + ): self._transformer = transformer if transformer is None: self._transformer = ParameterTransformer() @@ -156,22 +168,22 @@ class TransformationInjector(object): self._condition_builder, placeholder_names=generated_names, placeholder_values=generated_values, - is_key_condition=False + is_key_condition=False, ) self._transformer.transform( - params, model.input_shape, transformation, - 'ConditionExpression') + params, model.input_shape, transformation, 'ConditionExpression' + ) # Create and apply the Key Condition Expression transformation. transformation = ConditionExpressionTransformation( self._condition_builder, placeholder_names=generated_names, placeholder_values=generated_values, - is_key_condition=True + is_key_condition=True, ) self._transformer.transform( - params, model.input_shape, transformation, - 'KeyExpression') + params, model.input_shape, transformation, 'KeyExpression' + ) expr_attr_names_input = 'ExpressionAttributeNames' expr_attr_values_input = 'ExpressionAttributeValues' @@ -193,26 +205,37 @@ class TransformationInjector(object): def inject_attribute_value_input(self, params, model, **kwargs): """Injects DynamoDB serialization into parameter input""" self._transformer.transform( - params, model.input_shape, self._serializer.serialize, - 'AttributeValue') + params, + model.input_shape, + self._serializer.serialize, + 'AttributeValue', + ) def inject_attribute_value_output(self, parsed, model, **kwargs): """Injects DynamoDB deserialization into responses""" if model.output_shape is not None: self._transformer.transform( - parsed, model.output_shape, self._deserializer.deserialize, - 'AttributeValue' + parsed, + model.output_shape, + self._deserializer.deserialize, + 'AttributeValue', ) -class ConditionExpressionTransformation(object): +class ConditionExpressionTransformation: """Provides a transformation for condition expressions The ``ParameterTransformer`` class can call this class directly to transform the condition expressions in the parameters provided. """ - def __init__(self, condition_builder, placeholder_names, - placeholder_values, is_key_condition=False): + + def __init__( + self, + condition_builder, + placeholder_names, + placeholder_values, + is_key_condition=False, + ): self._condition_builder = condition_builder self._placeholder_names = placeholder_names self._placeholder_values = placeholder_values @@ -223,19 +246,22 @@ class ConditionExpressionTransformation(object): # Create a conditional expression string with placeholders # for the provided condition. built_expression = self._condition_builder.build_expression( - value, is_key_condition=self._is_key_condition) + value, is_key_condition=self._is_key_condition + ) self._placeholder_names.update( - built_expression.attribute_name_placeholders) + built_expression.attribute_name_placeholders + ) self._placeholder_values.update( - built_expression.attribute_value_placeholders) + built_expression.attribute_value_placeholders + ) return built_expression.condition_expression # Use the user provided value if it is not a ConditonBase object. return value -class ParameterTransformer(object): +class ParameterTransformer: """Transforms the input to and output from botocore based on shape""" def transform(self, params, model, transformation, target_shape): @@ -250,18 +276,20 @@ class ParameterTransformer(object): :param target_shape: The name of the shape to apply the transformation to """ - self._transform_parameters( - model, params, transformation, target_shape) + self._transform_parameters(model, params, transformation, target_shape) - def _transform_parameters(self, model, params, transformation, - target_shape): + def _transform_parameters( + self, model, params, transformation, target_shape + ): type_name = model.type_name - if type_name in ['structure', 'map', 'list']: - getattr(self, '_transform_%s' % type_name)( - model, params, transformation, target_shape) + if type_name in ('structure', 'map', 'list'): + getattr(self, f'_transform_{type_name}')( + model, params, transformation, target_shape + ) - def _transform_structure(self, model, params, transformation, - target_shape): + def _transform_structure( + self, model, params, transformation, target_shape + ): if not isinstance(params, collections_abc.Mapping): return for param in params: @@ -272,8 +300,11 @@ class ParameterTransformer(object): params[param] = transformation(params[param]) else: self._transform_parameters( - member_model, params[param], transformation, - target_shape) + member_model, + params[param], + transformation, + target_shape, + ) def _transform_map(self, model, params, transformation, target_shape): if not isinstance(params, collections_abc.Mapping): @@ -285,7 +316,8 @@ class ParameterTransformer(object): params[key] = transformation(value) else: self._transform_parameters( - value_model, params[key], transformation, target_shape) + value_model, params[key], transformation, target_shape + ) def _transform_list(self, model, params, transformation, target_shape): if not isinstance(params, collections_abc.MutableSequence): @@ -297,4 +329,5 @@ class ParameterTransformer(object): params[i] = transformation(item) else: self._transform_parameters( - member_model, params[i], transformation, target_shape) + member_model, params[i], transformation, target_shape + ) diff --git a/boto3/dynamodb/types.py b/boto3/dynamodb/types.py index 16721aa..f696a6d 100644 --- a/boto3/dynamodb/types.py +++ b/boto3/dynamodb/types.py @@ -10,13 +10,16 @@ # 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. -from decimal import Decimal, Context, Clamped -from decimal import Overflow, Inexact, Underflow, Rounded - -from boto3.compat import collections_abc - -from botocore.compat import six - +import collections.abc +from decimal import ( + Clamped, + Context, + Decimal, + Inexact, + Overflow, + Rounded, + Underflow, +) STRING = 'S' NUMBER = 'N' @@ -31,24 +34,28 @@ LIST = 'L' DYNAMODB_CONTEXT = Context( - Emin=-128, Emax=126, prec=38, - traps=[Clamped, Overflow, Inexact, Rounded, Underflow]) + Emin=-128, + Emax=126, + prec=38, + traps=[Clamped, Overflow, Inexact, Rounded, Underflow], +) -BINARY_TYPES = (bytearray, six.binary_type) +BINARY_TYPES = (bytearray, bytes) -class Binary(object): +class Binary: """A class for representing Binary in dynamodb Especially for Python 2, use this class to explicitly specify binary data for item in DynamoDB. It is essentially a wrapper around binary. Unicode and Python 3 string types are not allowed. """ + def __init__(self, value): if not isinstance(value, BINARY_TYPES): - raise TypeError('Value must be of the following types: %s.' % - ', '.join([str(t) for t in BINARY_TYPES])) + types = ', '.join([str(t) for t in BINARY_TYPES]) + raise TypeError(f'Value must be of the following types: {types}') self.value = value def __eq__(self, other): @@ -60,7 +67,7 @@ class Binary(object): return not self.__eq__(other) def __repr__(self): - return 'Binary(%r)' % self.value + return f'Binary({self.value!r})' def __str__(self): return self.value @@ -72,8 +79,9 @@ class Binary(object): return hash(self.value) -class TypeSerializer(object): +class TypeSerializer: """This class serializes Python data types to DynamoDB types.""" + def serialize(self, value): """The method to serialize the Python data types. @@ -103,7 +111,7 @@ class TypeSerializer(object): dictionaries can be directly passed to botocore methods. """ dynamodb_type = self._get_dynamodb_type(value) - serializer = getattr(self, '_serialize_%s' % dynamodb_type.lower()) + serializer = getattr(self, f'_serialize_{dynamodb_type}'.lower()) return {dynamodb_type: serializer(value)} def _get_dynamodb_type(self, value): @@ -140,7 +148,7 @@ class TypeSerializer(object): dynamodb_type = LIST else: - msg = 'Unsupported type "%s" for value "%s"' % (type(value), value) + msg = f'Unsupported type "{type(value)}" for value "{value}"' raise TypeError(msg) return dynamodb_type @@ -156,29 +164,26 @@ class TypeSerializer(object): return False def _is_number(self, value): - if isinstance(value, (six.integer_types, Decimal)): + if isinstance(value, (int, Decimal)): return True elif isinstance(value, float): raise TypeError( - 'Float types are not supported. Use Decimal types instead.') + 'Float types are not supported. Use Decimal types instead.' + ) return False def _is_string(self, value): - if isinstance(value, six.string_types): + if isinstance(value, str): return True return False def _is_binary(self, value): - if isinstance(value, Binary): - return True - elif isinstance(value, bytearray): - return True - elif six.PY3 and isinstance(value, six.binary_type): + if isinstance(value, (Binary, bytearray, bytes)): return True return False def _is_set(self, value): - if isinstance(value, collections_abc.Set): + if isinstance(value, collections.abc.Set): return True return False @@ -189,7 +194,7 @@ class TypeSerializer(object): return False def _is_map(self, value): - if isinstance(value, collections_abc.Mapping): + if isinstance(value, collections.abc.Mapping): return True return False @@ -231,11 +236,12 @@ class TypeSerializer(object): return [self.serialize(v) for v in value] def _serialize_m(self, value): - return dict([(k, self.serialize(v)) for k, v in value.items()]) + return {k: self.serialize(v) for k, v in value.items()} -class TypeDeserializer(object): +class TypeDeserializer: """This class deserializes DynamoDB types to Python types.""" + def deserialize(self, value): """The method to deserialize the DynamoDB data types. @@ -259,15 +265,17 @@ class TypeDeserializer(object): """ if not value: - raise TypeError('Value must be a nonempty dictionary whose key ' - 'is a valid dynamodb type.') + raise TypeError( + 'Value must be a nonempty dictionary whose key ' + 'is a valid dynamodb type.' + ) dynamodb_type = list(value.keys())[0] try: deserializer = getattr( - self, '_deserialize_%s' % dynamodb_type.lower()) + self, f'_deserialize_{dynamodb_type}'.lower() + ) except AttributeError: - raise TypeError( - 'Dynamodb type %s is not supported' % dynamodb_type) + raise TypeError(f'Dynamodb type {dynamodb_type} is not supported') return deserializer(value[dynamodb_type]) def _deserialize_null(self, value): @@ -298,4 +306,4 @@ class TypeDeserializer(object): return [self.deserialize(v) for v in value] def _deserialize_m(self, value): - return dict([(k, self.deserialize(v)) for k, v in value.items()]) + return {k: self.deserialize(v) for k, v in value.items()} diff --git a/boto3/ec2/deletetags.py b/boto3/ec2/deletetags.py index ebbbfc9..19876d0 100644 --- a/boto3/ec2/deletetags.py +++ b/boto3/ec2/deletetags.py @@ -17,15 +17,18 @@ def inject_delete_tags(event_emitter, **kwargs): action_model = { 'request': { 'operation': 'DeleteTags', - 'params': [{ - 'target': 'Resources[0]', - 'source': 'identifier', - 'name': 'Id' - }] + 'params': [ + { + 'target': 'Resources[0]', + 'source': 'identifier', + 'name': 'Id', + } + ], } } action = CustomModeledAction( - 'delete_tags', action_model, delete_tags, event_emitter) + 'delete_tags', action_model, delete_tags, event_emitter + ) action.inject(**kwargs) diff --git a/boto3/examples/s3.rst b/boto3/examples/s3.rst index 147c4a3..0a79fb0 100644 --- a/boto3/examples/s3.rst +++ b/boto3/examples/s3.rst @@ -144,3 +144,42 @@ Boto3 will automatically compute this value for us. SSECustomerAlgorithm='AES256') print("Done, response body:") print(response['Body'].read()) + + +Downloading a specific version of an S3 object +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example shows how to download a specific version of an +S3 object. + +.. code-block:: python + + import boto3 + s3 = boto3.client('s3') + + s3.download_file( + "bucket-name", "key-name", "tmp.txt", + ExtraArgs={"VersionId": "my-version-id"} + ) + + +Filter objects by last modified time using JMESPath +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example shows how to filter objects by last modified time +using JMESPath. + +.. code-block:: python + + import boto3 + s3 = boto3.client("s3") + + s3_paginator = s3.get_paginator('list_objects_v2') + s3_iterator = s3_paginator.paginate(Bucket='your-bucket-name') + + filtered_iterator = s3_iterator.search( + "Contents[?to_string(LastModified)>='\"2022-01-05 08:05:37+00:00\"'].Key" + ) + + for key_data in filtered_iterator: + print(key_data) diff --git a/boto3/exceptions.py b/boto3/exceptions.py index 9120577..0068de9 100644 --- a/boto3/exceptions.py +++ b/boto3/exceptions.py @@ -35,33 +35,37 @@ class NoVersionFound(Boto3Error): # this low level Botocore error before this exception was # introduced in boto3. # Same thing for ResourceNotExistsError below. -class UnknownAPIVersionError(Boto3Error, - botocore.exceptions.DataNotFoundError): - def __init__(self, service_name, bad_api_version, - available_api_versions): +class UnknownAPIVersionError( + Boto3Error, botocore.exceptions.DataNotFoundError +): + def __init__(self, service_name, bad_api_version, available_api_versions): msg = ( - "The '%s' resource does not an API version of: %s\n" - "Valid API versions are: %s" - % (service_name, bad_api_version, available_api_versions) + f"The '{service_name}' resource does not an API version of: {bad_api_version}\n" + f"Valid API versions are: {available_api_versions}" ) # Not using super because we don't want the DataNotFoundError # to be called, it has a different __init__ signature. Boto3Error.__init__(self, msg) -class ResourceNotExistsError(Boto3Error, - botocore.exceptions.DataNotFoundError): +class ResourceNotExistsError( + Boto3Error, botocore.exceptions.DataNotFoundError +): """Raised when you attempt to create a resource that does not exist.""" + def __init__(self, service_name, available_services, has_low_level_client): msg = ( - "The '%s' resource does not exist.\n" + "The '{}' resource does not exist.\n" "The available resources are:\n" - " - %s\n" % (service_name, '\n - '.join(available_services)) + " - {}\n".format( + service_name, '\n - '.join(available_services) + ) ) if has_low_level_client: - msg += ( - "\nConsider using a boto3.client('%s') instead " - "of a resource for '%s'" % (service_name, service_name)) + msg = ( + f"{msg}\nConsider using a boto3.client('{service_name}') " + f"instead of a resource for '{service_name}'" + ) # Not using super because we don't want the DataNotFoundError # to be called, it has a different __init__ signature. Boto3Error.__init__(self, msg) @@ -69,7 +73,7 @@ class ResourceNotExistsError(Boto3Error, class RetriesExceededError(Boto3Error): def __init__(self, last_exception, msg='Max Retries Exceeded'): - super(RetriesExceededError, self).__init__(msg) + super().__init__(msg) self.last_exception = last_exception @@ -83,12 +87,13 @@ class S3UploadFailedError(Boto3Error): class DynamoDBOperationNotSupportedError(Boto3Error): """Raised for operations that are not supported for an operand.""" + def __init__(self, operation, value): msg = ( - '%s operation cannot be applied to value %s of type %s directly. ' - 'Must use AttributeBase object methods (i.e. Attr().eq()). to ' - 'generate ConditionBase instances first.' % - (operation, value, type(value))) + f'{operation} operation cannot be applied to value {value} of type ' + f'{type(value)} directly. Must use AttributeBase object methods ' + f'(i.e. Attr().eq()). to generate ConditionBase instances first.' + ) Exception.__init__(self, msg) @@ -98,11 +103,13 @@ DynanmoDBOperationNotSupportedError = DynamoDBOperationNotSupportedError class DynamoDBNeedsConditionError(Boto3Error): """Raised when input is not a condition""" + def __init__(self, value): msg = ( - 'Expecting a ConditionBase object. Got %s of type %s. ' - 'Use AttributeBase object methods (i.e. Attr().eq()). to ' - 'generate ConditionBase instances.' % (value, type(value))) + f'Expecting a ConditionBase object. Got {value} of type {type(value)}. ' + f'Use AttributeBase object methods (i.e. Attr().eq()). to ' + f'generate ConditionBase instances.' + ) Exception.__init__(self, msg) @@ -115,4 +122,5 @@ class PythonDeprecationWarning(Warning): Python version being used is scheduled to become unsupported in an future release. See warning for specifics. """ + pass diff --git a/boto3/resources/action.py b/boto3/resources/action.py index 3fefe90..7c7d839 100644 --- a/boto3/resources/action.py +++ b/boto3/resources/action.py @@ -15,18 +15,17 @@ import logging from botocore import xform_name -from .params import create_request_parameters -from .response import RawHandler, ResourceHandler -from .model import Action - from boto3.docs.docstring import ActionDocstring from boto3.utils import inject_attribute +from .model import Action +from .params import create_request_parameters +from .response import RawHandler, ResourceHandler logger = logging.getLogger(__name__) -class ServiceAction(object): +class ServiceAction: """ A class representing a callable action on a resource, for example ``sqs.get_queue_by_name(...)`` or ``s3.Bucket('foo').delete()``. @@ -43,6 +42,7 @@ class ServiceAction(object): :type service_context: :py:class:`~boto3.utils.ServiceContext` :param service_context: Context about the AWS service """ + def __init__(self, action_model, factory=None, service_context=None): self._action_model = action_model @@ -52,9 +52,10 @@ class ServiceAction(object): if resource_response_model: self._response_handler = ResourceHandler( search_path=resource_response_model.path, - factory=factory, resource_model=resource_response_model, + factory=factory, + resource_model=resource_response_model, service_context=service_context, - operation_name=action_model.request.operation + operation_name=action_model.request.operation, ) else: self._response_handler = RawHandler(action_model.path) @@ -77,8 +78,12 @@ class ServiceAction(object): params = create_request_parameters(parent, self._action_model.request) params.update(kwargs) - logger.debug('Calling %s:%s with %r', parent.meta.service_name, - operation_name, params) + logger.debug( + 'Calling %s:%s with %r', + parent.meta.service_name, + operation_name, + params, + ) response = getattr(parent.meta.client, operation_name)(*args, **params) @@ -105,6 +110,7 @@ class BatchAction(ServiceAction): :type service_context: :py:class:`~boto3.utils.ServiceContext` :param service_context: Context about the AWS service """ + def __call__(self, parent, *args, **kwargs): """ Perform the batch action's operation on every page of results @@ -137,8 +143,11 @@ class BatchAction(ServiceAction): client = resource.meta.client create_request_parameters( - resource, self._action_model.request, - params=params, index=index) + resource, + self._action_model.request, + params=params, + index=index, + ) if not params: # There are no items, no need to make a call. @@ -146,20 +155,20 @@ class BatchAction(ServiceAction): params.update(kwargs) - logger.debug('Calling %s:%s with %r', - service_name, operation_name, params) + logger.debug( + 'Calling %s:%s with %r', service_name, operation_name, params + ) response = getattr(client, operation_name)(*args, **params) logger.debug('Response: %r', response) - responses.append( - self._response_handler(parent, params, response)) + responses.append(self._response_handler(parent, params, response)) return responses -class WaiterAction(object): +class WaiterAction: """ A class representing a callable waiter action on a resource, for example ``s3.Bucket('foo').wait_until_bucket_exists()``. @@ -173,6 +182,7 @@ class WaiterAction(object): resource. It usually begins with a ``wait_until_`` """ + def __init__(self, waiter_model, waiter_resource_name): self._waiter_model = waiter_model self._waiter_resource_name = waiter_resource_name @@ -193,9 +203,12 @@ class WaiterAction(object): params = create_request_parameters(parent, self._waiter_model) params.update(kwargs) - logger.debug('Calling %s:%s with %r', - parent.meta.service_name, - self._waiter_resource_name, params) + logger.debug( + 'Calling %s:%s with %r', + parent.meta.service_name, + self._waiter_resource_name, + params, + ) client = parent.meta.client waiter = client.get_waiter(client_waiter_name) @@ -204,10 +217,10 @@ class WaiterAction(object): logger.debug('Response: %r', response) -class CustomModeledAction(object): +class CustomModeledAction: """A custom, modeled action to inject into a resource.""" - def __init__(self, action_name, action_model, - function, event_emitter): + + def __init__(self, action_name, action_model, function, event_emitter): """ :type action_name: str :param action_name: The name of the action to inject, e.g. @@ -239,6 +252,6 @@ class CustomModeledAction(object): event_emitter=self.emitter, action_model=action, service_model=service_context.service_model, - include_signature=False + include_signature=False, ) inject_attribute(class_attributes, self.name, self.function) diff --git a/boto3/resources/base.py b/boto3/resources/base.py index b77bd98..c398280 100644 --- a/boto3/resources/base.py +++ b/boto3/resources/base.py @@ -15,16 +15,22 @@ import logging import boto3 - logger = logging.getLogger(__name__) -class ResourceMeta(object): +class ResourceMeta: """ An object containing metadata about a resource. """ - def __init__(self, service_name, identifiers=None, client=None, - data=None, resource_model=None): + + def __init__( + self, + service_name, + identifiers=None, + client=None, + data=None, + resource_model=None, + ): #: (``string``) The service name, e.g. 's3' self.service_name = service_name @@ -42,8 +48,9 @@ class ResourceMeta(object): self.resource_model = resource_model def __repr__(self): - return 'ResourceMeta(\'{0}\', identifiers={1})'.format( - self.service_name, self.identifiers) + return 'ResourceMeta(\'{}\', identifiers={})'.format( + self.service_name, self.identifiers + ) def __eq__(self, other): # Two metas are equal if their components are all equal @@ -61,7 +68,7 @@ class ResourceMeta(object): return ResourceMeta(service_name, **params) -class ServiceResource(object): +class ServiceResource: """ A base class for resources. @@ -108,22 +115,22 @@ class ServiceResource(object): continue if name not in self.meta.identifiers: - raise ValueError('Unknown keyword argument: {0}'.format(name)) + raise ValueError(f'Unknown keyword argument: {name}') setattr(self, '_' + name, value) # Validate that all identifiers have been set. for identifier in self.meta.identifiers: if getattr(self, identifier) is None: - raise ValueError( - 'Required parameter {0} not set'.format(identifier)) + raise ValueError(f'Required parameter {identifier} not set') def __repr__(self): identifiers = [] for identifier in self.meta.identifiers: - identifiers.append('{0}={1}'.format( - identifier, repr(getattr(self, identifier)))) - return "{0}({1})".format( + identifiers.append( + f'{identifier}={repr(getattr(self, identifier))}' + ) + return "{}({})".format( self.__class__.__name__, ', '.join(identifiers), ) diff --git a/boto3/resources/collection.py b/boto3/resources/collection.py index 2118e57..7f7862e 100644 --- a/boto3/resources/collection.py +++ b/boto3/resources/collection.py @@ -17,16 +17,15 @@ import logging from botocore import xform_name from botocore.utils import merge_dicts +from ..docs import docstring from .action import BatchAction from .params import create_request_parameters from .response import ResourceHandler -from ..docs import docstring - logger = logging.getLogger(__name__) -class ResourceCollection(object): +class ResourceCollection: """ Represents a collection of resources, which can be iterated through, optionally with filtering. Collections automatically handle pagination @@ -43,22 +42,21 @@ class ResourceCollection(object): :param handler: The resource response handler used to create resource instances """ + def __init__(self, model, parent, handler, **kwargs): self._model = model self._parent = parent - self._py_operation_name = xform_name( - model.request.operation) + self._py_operation_name = xform_name(model.request.operation) self._handler = handler self._params = copy.deepcopy(kwargs) def __repr__(self): - return '{0}({1}, {2})'.format( + return '{}({}, {})'.format( self.__class__.__name__, self._parent, - '{0}.{1}'.format( - self._parent.meta.service_name, - self._model.resource.type - ) + '{}.{}'.format( + self._parent.meta.service_name, self._model.resource.type + ), ) def __iter__(self): @@ -109,8 +107,9 @@ class ResourceCollection(object): """ params = copy.deepcopy(self._params) merge_dicts(params, kwargs, append_lists=True) - clone = self.__class__(self._model, self._parent, - self._handler, **params) + clone = self.__class__( + self._model, self._parent, self._handler, **params + ) return clone def pages(self): @@ -137,8 +136,7 @@ class ResourceCollection(object): cleaned_params = self._params.copy() limit = cleaned_params.pop('limit', None) page_size = cleaned_params.pop('page_size', None) - params = create_request_parameters( - self._parent, self._model.request) + params = create_request_parameters(self._parent, self._model.request) merge_dicts(params, cleaned_params, append_lists=True) # Is this a paginated operation? If so, we need to get an @@ -147,17 +145,24 @@ class ResourceCollection(object): # page in a list. For non-paginated results, we just ignore # the page size parameter. if client.can_paginate(self._py_operation_name): - logger.debug('Calling paginated %s:%s with %r', - self._parent.meta.service_name, - self._py_operation_name, params) + logger.debug( + 'Calling paginated %s:%s with %r', + self._parent.meta.service_name, + self._py_operation_name, + params, + ) paginator = client.get_paginator(self._py_operation_name) pages = paginator.paginate( - PaginationConfig={ - 'MaxItems': limit, 'PageSize': page_size}, **params) + PaginationConfig={'MaxItems': limit, 'PageSize': page_size}, + **params + ) else: - logger.debug('Calling %s:%s with %r', - self._parent.meta.service_name, - self._py_operation_name, params) + logger.debug( + 'Calling %s:%s with %r', + self._parent.meta.service_name, + self._py_operation_name, + params, + ) pages = [getattr(client, self._py_operation_name)(**params)] # Now that we have a page iterator or single page of results @@ -257,7 +262,7 @@ class ResourceCollection(object): return self._clone(page_size=count) -class CollectionManager(object): +class CollectionManager: """ A collection manager provides access to resource collection instances, which can be iterated and filtered. The manager exposes some @@ -300,6 +305,7 @@ class CollectionManager(object): :type service_context: :py:class:`~boto3.utils.ServiceContext` :param service_context: Context about the AWS service """ + # The class to use when creating an iterator _collection_cls = ResourceCollection @@ -310,20 +316,20 @@ class CollectionManager(object): search_path = collection_model.resource.path self._handler = ResourceHandler( - search_path=search_path, factory=factory, + search_path=search_path, + factory=factory, resource_model=collection_model.resource, service_context=service_context, - operation_name=operation_name + operation_name=operation_name, ) def __repr__(self): - return '{0}({1}, {2})'.format( + return '{}({}, {})'.format( self.__class__.__name__, self._parent, - '{0}.{1}'.format( - self._parent.meta.service_name, - self._model.resource.type - ) + '{}.{}'.format( + self._parent.meta.service_name, self._model.resource.type + ), ) def iterator(self, **kwargs): @@ -333,40 +339,48 @@ class CollectionManager(object): :rtype: :py:class:`ResourceCollection` :return: An iterable representing the collection of resources """ - return self._collection_cls(self._model, self._parent, - self._handler, **kwargs) + return self._collection_cls( + self._model, self._parent, self._handler, **kwargs + ) # Set up some methods to proxy ResourceCollection methods def all(self): return self.iterator() + all.__doc__ = ResourceCollection.all.__doc__ def filter(self, **kwargs): return self.iterator(**kwargs) + filter.__doc__ = ResourceCollection.filter.__doc__ def limit(self, count): return self.iterator(limit=count) + limit.__doc__ = ResourceCollection.limit.__doc__ def page_size(self, count): return self.iterator(page_size=count) + page_size.__doc__ = ResourceCollection.page_size.__doc__ def pages(self): return self.iterator().pages() + pages.__doc__ = ResourceCollection.pages.__doc__ -class CollectionFactory(object): +class CollectionFactory: """ A factory to create new :py:class:`CollectionManager` and :py:class:`ResourceCollection` subclasses from a :py:class:`~boto3.resources.model.Collection` model. These subclasses include methods to perform batch operations. """ - def load_from_definition(self, resource_name, collection_model, - service_context, event_emitter): + + def load_from_definition( + self, resource_name, collection_model, service_context, event_emitter + ): """ Loads a collection from a model, creating a new :py:class:`CollectionManager` subclass @@ -393,40 +407,55 @@ class CollectionFactory(object): # Create the batch actions for a collection self._load_batch_actions( - attrs, resource_name, collection_model, - service_context.service_model, event_emitter) + attrs, + resource_name, + collection_model, + service_context.service_model, + event_emitter, + ) # Add the documentation to the collection class's methods self._load_documented_collection_methods( - attrs=attrs, resource_name=resource_name, + attrs=attrs, + resource_name=resource_name, collection_model=collection_model, service_model=service_context.service_model, event_emitter=event_emitter, - base_class=ResourceCollection) + base_class=ResourceCollection, + ) if service_context.service_name == resource_name: - cls_name = '{0}.{1}Collection'.format( - service_context.service_name, collection_name) + cls_name = '{}.{}Collection'.format( + service_context.service_name, collection_name + ) else: - cls_name = '{0}.{1}.{2}Collection'.format( - service_context.service_name, resource_name, collection_name) + cls_name = '{}.{}.{}Collection'.format( + service_context.service_name, resource_name, collection_name + ) - collection_cls = type(str(cls_name), (ResourceCollection,), - attrs) + collection_cls = type(str(cls_name), (ResourceCollection,), attrs) # Add the documentation to the collection manager's methods self._load_documented_collection_methods( - attrs=attrs, resource_name=resource_name, + attrs=attrs, + resource_name=resource_name, collection_model=collection_model, service_model=service_context.service_model, event_emitter=event_emitter, - base_class=CollectionManager) + base_class=CollectionManager, + ) attrs['_collection_cls'] = collection_cls cls_name += 'Manager' return type(str(cls_name), (CollectionManager,), attrs) - def _load_batch_actions(self, attrs, resource_name, collection_model, - service_model, event_emitter): + def _load_batch_actions( + self, + attrs, + resource_name, + collection_model, + service_model, + event_emitter, + ): """ Batch actions on the collection become methods on both the collection manager and iterators. @@ -434,12 +463,23 @@ class CollectionFactory(object): for action_model in collection_model.batch_actions: snake_cased = xform_name(action_model.name) attrs[snake_cased] = self._create_batch_action( - resource_name, snake_cased, action_model, collection_model, - service_model, event_emitter) + resource_name, + snake_cased, + action_model, + collection_model, + service_model, + event_emitter, + ) def _load_documented_collection_methods( - factory_self, attrs, resource_name, collection_model, - service_model, event_emitter, base_class): + factory_self, + attrs, + resource_name, + collection_model, + service_model, + event_emitter, + base_class, + ): # The base class already has these methods defined. However # the docstrings are generic and not based for a particular service # or resource. So we override these methods by proxying to the @@ -456,7 +496,7 @@ class CollectionFactory(object): event_emitter=event_emitter, collection_model=collection_model, service_model=service_model, - include_signature=False + include_signature=False, ) attrs['all'] = all @@ -470,7 +510,7 @@ class CollectionFactory(object): event_emitter=event_emitter, collection_model=collection_model, service_model=service_model, - include_signature=False + include_signature=False, ) attrs['filter'] = filter @@ -484,7 +524,7 @@ class CollectionFactory(object): event_emitter=event_emitter, collection_model=collection_model, service_model=service_model, - include_signature=False + include_signature=False, ) attrs['limit'] = limit @@ -498,13 +538,19 @@ class CollectionFactory(object): event_emitter=event_emitter, collection_model=collection_model, service_model=service_model, - include_signature=False + include_signature=False, ) attrs['page_size'] = page_size - def _create_batch_action(factory_self, resource_name, snake_cased, - action_model, collection_model, service_model, - event_emitter): + def _create_batch_action( + factory_self, + resource_name, + snake_cased, + action_model, + collection_model, + service_model, + event_emitter, + ): """ Creates a new method which makes a batch operation request to the underlying service API. @@ -521,6 +567,6 @@ class CollectionFactory(object): batch_action_model=action_model, service_model=service_model, collection_model=collection_model, - include_signature=False + include_signature=False, ) return batch_action diff --git a/boto3/resources/factory.py b/boto3/resources/factory.py index 90b3ce9..1d33e5c 100644 --- a/boto3/resources/factory.py +++ b/boto3/resources/factory.py @@ -14,20 +14,18 @@ import logging from functools import partial -from .action import ServiceAction -from .action import WaiterAction +from ..docs import docstring +from ..exceptions import ResourceLoadException +from .action import ServiceAction, WaiterAction from .base import ResourceMeta, ServiceResource from .collection import CollectionFactory from .model import ResourceModel -from .response import build_identifiers, ResourceHandler -from ..exceptions import ResourceLoadException -from ..docs import docstring - +from .response import ResourceHandler, build_identifiers logger = logging.getLogger(__name__) -class ResourceFactory(object): +class ResourceFactory: """ A factory to create new :py:class:`~boto3.resources.base.ServiceResource` classes from a :py:class:`~boto3.resources.model.ResourceModel`. There are @@ -35,12 +33,14 @@ class ResourceFactory(object): SQS resource) and another on models contained within the service (e.g. an SQS Queue resource). """ + def __init__(self, emitter): self._collection_factory = CollectionFactory() self._emitter = emitter - def load_from_definition(self, resource_name, - single_resource_json_definition, service_context): + def load_from_definition( + self, resource_name, single_resource_json_definition, service_context + ): """ Loads a resource from a model, creating a new :py:class:`~boto3.resources.base.ServiceResource` subclass @@ -62,13 +62,15 @@ class ResourceFactory(object): :rtype: Subclass of :py:class:`~boto3.resources.base.ServiceResource` :return: The service or resource class. """ - logger.debug('Loading %s:%s', service_context.service_name, - resource_name) + logger.debug( + 'Loading %s:%s', service_context.service_name, resource_name + ) # Using the loaded JSON create a ResourceModel object. resource_model = ResourceModel( - resource_name, single_resource_json_definition, - service_context.resource_json_definitions + resource_name, + single_resource_json_definition, + service_context.resource_json_definitions, ) # Do some renaming of the shape if there was a naming collision @@ -76,12 +78,14 @@ class ResourceFactory(object): shape = None if resource_model.shape: shape = service_context.service_model.shape_for( - resource_model.shape) + resource_model.shape + ) resource_model.load_rename_map(shape) # Set some basic info meta = ResourceMeta( - service_context.service_name, resource_model=resource_model) + service_context.service_name, resource_model=resource_model + ) attrs = { 'meta': meta, } @@ -91,37 +95,50 @@ class ResourceFactory(object): # Identifiers self._load_identifiers( - attrs=attrs, meta=meta, resource_name=resource_name, - resource_model=resource_model + attrs=attrs, + meta=meta, + resource_name=resource_name, + resource_model=resource_model, ) # Load/Reload actions self._load_actions( - attrs=attrs, resource_name=resource_name, - resource_model=resource_model, service_context=service_context + attrs=attrs, + resource_name=resource_name, + resource_model=resource_model, + service_context=service_context, ) # Attributes that get auto-loaded self._load_attributes( - attrs=attrs, meta=meta, resource_name=resource_name, + attrs=attrs, + meta=meta, + resource_name=resource_name, resource_model=resource_model, - service_context=service_context) + service_context=service_context, + ) # Collections and their corresponding methods self._load_collections( - attrs=attrs, resource_model=resource_model, - service_context=service_context) + attrs=attrs, + resource_model=resource_model, + service_context=service_context, + ) # References and Subresources self._load_has_relations( - attrs=attrs, resource_name=resource_name, - resource_model=resource_model, service_context=service_context + attrs=attrs, + resource_name=resource_name, + resource_model=resource_model, + service_context=service_context, ) # Waiter resource actions self._load_waiters( - attrs=attrs, resource_name=resource_name, - resource_model=resource_model, service_context=service_context + attrs=attrs, + resource_name=resource_name, + resource_model=resource_model, + service_context=service_context, ) # Create the name based on the requested service and resource @@ -133,9 +150,11 @@ class ResourceFactory(object): base_classes = [ServiceResource] if self._emitter is not None: self._emitter.emit( - 'creating-resource-class.%s' % cls_name, - class_attributes=attrs, base_classes=base_classes, - service_context=service_context) + f'creating-resource-class.{cls_name}', + class_attributes=attrs, + base_classes=base_classes, + service_context=service_context, + ) return type(str(cls_name), tuple(base_classes), attrs) def _load_identifiers(self, attrs, meta, resource_model, resource_name): @@ -147,10 +166,12 @@ class ResourceFactory(object): for identifier in resource_model.identifiers: meta.identifiers.append(identifier.name) attrs[identifier.name] = self._create_identifier( - identifier, resource_name) + identifier, resource_name + ) - def _load_actions(self, attrs, resource_name, resource_model, - service_context): + def _load_actions( + self, attrs, resource_name, resource_model, service_context + ): """ Actions on the resource become methods, with the ``load`` method being a special case which sets internal data for attributes, and @@ -158,17 +179,23 @@ class ResourceFactory(object): """ if resource_model.load: attrs['load'] = self._create_action( - action_model=resource_model.load, resource_name=resource_name, - service_context=service_context, is_load=True) + action_model=resource_model.load, + resource_name=resource_name, + service_context=service_context, + is_load=True, + ) attrs['reload'] = attrs['load'] for action in resource_model.actions: attrs[action.name] = self._create_action( - action_model=action, resource_name=resource_name, - service_context=service_context) + action_model=action, + resource_name=resource_name, + service_context=service_context, + ) - def _load_attributes(self, attrs, meta, resource_name, resource_model, - service_context): + def _load_attributes( + self, attrs, meta, resource_name, resource_model, service_context + ): """ Load resource attributes based on the resource shape. The shape name is referenced in the resource JSON, but the shape itself @@ -178,12 +205,13 @@ class ResourceFactory(object): if not resource_model.shape: return - shape = service_context.service_model.shape_for( - resource_model.shape) + shape = service_context.service_model.shape_for(resource_model.shape) - identifiers = dict( - (i.member_name, i) - for i in resource_model.identifiers if i.member_name) + identifiers = { + i.member_name: i + for i in resource_model.identifiers + if i.member_name + } attributes = resource_model.get_attributes(shape) for name, (orig_name, member) in attributes.items(): if name in identifiers: @@ -191,14 +219,15 @@ class ResourceFactory(object): resource_name=resource_name, identifier=identifiers[name], member_model=member, - service_context=service_context + service_context=service_context, ) else: prop = self._create_autoload_property( resource_name=resource_name, - name=orig_name, snake_cased=name, + name=orig_name, + snake_cased=name, member_model=member, - service_context=service_context + service_context=service_context, ) attrs[name] = prop @@ -213,11 +242,12 @@ class ResourceFactory(object): attrs[collection_model.name] = self._create_collection( resource_name=resource_model.name, collection_model=collection_model, - service_context=service_context + service_context=service_context, ) - def _load_has_relations(self, attrs, resource_name, resource_model, - service_context): + def _load_has_relations( + self, attrs, resource_name, resource_model, service_context + ): """ Load related resources, which are defined via a ``has`` relationship but conceptually come in two forms: @@ -235,7 +265,7 @@ class ResourceFactory(object): attrs[reference.name] = self._create_reference( reference_model=reference, resource_name=resource_name, - service_context=service_context + service_context=service_context, ) for subresource in resource_model.subresources: @@ -244,11 +274,12 @@ class ResourceFactory(object): attrs[subresource.name] = self._create_class_partial( subresource_model=subresource, resource_name=resource_name, - service_context=service_context + service_context=service_context, ) self._create_available_subresources_command( - attrs, resource_model.subresources) + attrs, resource_model.subresources + ) def _create_available_subresources_command(self, attrs, subresources): _subresources = [subresource.name for subresource in subresources] @@ -267,8 +298,9 @@ class ResourceFactory(object): attrs['get_available_subresources'] = get_available_subresources - def _load_waiters(self, attrs, resource_name, resource_model, - service_context): + def _load_waiters( + self, attrs, resource_name, resource_model, service_context + ): """ Load resource waiters from the model. Each waiter allows you to wait until a resource reaches a specific state by polling the state @@ -278,13 +310,14 @@ class ResourceFactory(object): attrs[waiter.name] = self._create_waiter( resource_waiter_model=waiter, resource_name=resource_name, - service_context=service_context + service_context=service_context, ) def _create_identifier(factory_self, identifier, resource_name): """ Creates a read-only property for identifier attributes. """ + def get_identifier(self): # The default value is set to ``None`` instead of # raising an AttributeError because when resources are @@ -298,16 +331,18 @@ class ResourceFactory(object): get_identifier.__doc__ = docstring.IdentifierDocstring( resource_name=resource_name, identifier_model=identifier, - include_signature=False + include_signature=False, ) return property(get_identifier) - def _create_identifier_alias(factory_self, resource_name, identifier, - member_model, service_context): + def _create_identifier_alias( + factory_self, resource_name, identifier, member_model, service_context + ): """ Creates a read-only property that aliases an identifier. """ + def get_identifier(self): return getattr(self, '_' + identifier.name, None) @@ -318,13 +353,19 @@ class ResourceFactory(object): attr_name=identifier.member_name, event_emitter=factory_self._emitter, attr_model=member_model, - include_signature=False + include_signature=False, ) return property(get_identifier) - def _create_autoload_property(factory_self, resource_name, name, - snake_cased, member_model, service_context): + def _create_autoload_property( + factory_self, + resource_name, + name, + snake_cased, + member_model, + service_context, + ): """ Creates a new property on the resource to lazy-load its value via the resource's ``load`` method (if it exists). @@ -339,8 +380,8 @@ class ResourceFactory(object): self.load() else: raise ResourceLoadException( - '{0} has no load method'.format( - self.__class__.__name__)) + f'{self.__class__.__name__} has no load method' + ) return self.meta.data.get(name) @@ -351,19 +392,22 @@ class ResourceFactory(object): attr_name=snake_cased, event_emitter=factory_self._emitter, attr_model=member_model, - include_signature=False + include_signature=False, ) return property(property_loader) - def _create_waiter(factory_self, resource_waiter_model, resource_name, - service_context): + def _create_waiter( + factory_self, resource_waiter_model, resource_name, service_context + ): """ Creates a new wait method for each resource where both a waiter and resource model is defined. """ - waiter = WaiterAction(resource_waiter_model, - waiter_resource_name=resource_waiter_model.name) + waiter = WaiterAction( + resource_waiter_model, + waiter_resource_name=resource_waiter_model.name, + ) def do_waiter(self, *args, **kwargs): waiter(self, *args, **kwargs) @@ -375,32 +419,40 @@ class ResourceFactory(object): service_model=service_context.service_model, resource_waiter_model=resource_waiter_model, service_waiter_model=service_context.service_waiter_model, - include_signature=False + include_signature=False, ) return do_waiter - def _create_collection(factory_self, resource_name, collection_model, - service_context): + def _create_collection( + factory_self, resource_name, collection_model, service_context + ): """ Creates a new property on the resource to lazy-load a collection. """ cls = factory_self._collection_factory.load_from_definition( - resource_name=resource_name, collection_model=collection_model, + resource_name=resource_name, + collection_model=collection_model, service_context=service_context, - event_emitter=factory_self._emitter) + event_emitter=factory_self._emitter, + ) def get_collection(self): return cls( - collection_model=collection_model, parent=self, - factory=factory_self, service_context=service_context) + collection_model=collection_model, + parent=self, + factory=factory_self, + service_context=service_context, + ) get_collection.__name__ = str(collection_model.name) get_collection.__doc__ = docstring.CollectionDocstring( - collection_model=collection_model, include_signature=False) + collection_model=collection_model, include_signature=False + ) return property(get_collection) - def _create_reference(factory_self, reference_model, resource_name, - service_context): + def _create_reference( + factory_self, reference_model, resource_name, service_context + ): """ Creates a new property on the resource to lazy-load a reference. """ @@ -408,16 +460,18 @@ class ResourceFactory(object): # or response, so we can re-use the response handlers to # build up resources from identifiers and data members. handler = ResourceHandler( - search_path=reference_model.resource.path, factory=factory_self, + search_path=reference_model.resource.path, + factory=factory_self, resource_model=reference_model.resource, - service_context=service_context + service_context=service_context, ) # Are there any identifiers that need access to data members? # This is important when building the resource below since # it requires the data to be loaded. - needs_data = any(i.source == 'data' for i in - reference_model.resource.identifiers) + needs_data = any( + i.source == 'data' for i in reference_model.resource.identifiers + ) def get_reference(self): # We need to lazy-evaluate the reference to handle circular @@ -433,13 +487,13 @@ class ResourceFactory(object): get_reference.__name__ = str(reference_model.name) get_reference.__doc__ = docstring.ReferenceDocstring( - reference_model=reference_model, - include_signature=False + reference_model=reference_model, include_signature=False ) return property(get_reference) - def _create_class_partial(factory_self, subresource_model, resource_name, - service_context): + def _create_class_partial( + factory_self, subresource_model, resource_name, service_context + ): """ Creates a new method which acts as a functools.partial, passing along the instance's low-level `client` to the new resource @@ -457,7 +511,7 @@ class ResourceFactory(object): resource_cls = factory_self.load_from_definition( resource_name=name, single_resource_json_definition=json_def, - service_context=service_context + service_context=service_context, ) # Assumes that identifiers are in order, which lets you do @@ -470,20 +524,26 @@ class ResourceFactory(object): for identifier, value in build_identifiers(identifiers, self): positional_args.append(value) - return partial(resource_cls, *positional_args, - client=self.meta.client)(*args, **kwargs) + return partial( + resource_cls, *positional_args, client=self.meta.client + )(*args, **kwargs) create_resource.__name__ = str(name) create_resource.__doc__ = docstring.SubResourceDocstring( resource_name=resource_name, sub_resource_model=subresource_model, service_model=service_context.service_model, - include_signature=False + include_signature=False, ) return create_resource - def _create_action(factory_self, action_model, resource_name, - service_context, is_load=False): + def _create_action( + factory_self, + action_model, + resource_name, + service_context, + is_load=False, + ): """ Creates a new method which makes a request to the underlying AWS service. @@ -492,8 +552,7 @@ class ResourceFactory(object): # method below is invoked, which allows instances of the resource # to share the ServiceAction instance. action = ServiceAction( - action_model, factory=factory_self, - service_context=service_context + action_model, factory=factory_self, service_context=service_context ) # A resource's ``load`` method is special because it sets @@ -504,6 +563,7 @@ class ResourceFactory(object): def do_action(self, *args, **kwargs): response = action(self, *args, **kwargs) self.meta.data = response + # Create the docstring for the load/reload mehtods. lazy_docstring = docstring.LoadReloadDocstring( action_name=action_model.name, @@ -511,7 +571,7 @@ class ResourceFactory(object): event_emitter=factory_self._emitter, load_model=action_model, service_model=service_context.service_model, - include_signature=False + include_signature=False, ) else: # We need a new method here because we want access to the @@ -526,12 +586,13 @@ class ResourceFactory(object): self.meta.data = None return response + lazy_docstring = docstring.ActionDocstring( resource_name=resource_name, event_emitter=factory_self._emitter, action_model=action_model, service_model=service_context.service_model, - include_signature=False + include_signature=False, ) do_action.__name__ = str(action_model.name) diff --git a/boto3/resources/model.py b/boto3/resources/model.py index 478d265..29371ee 100644 --- a/boto3/resources/model.py +++ b/boto3/resources/model.py @@ -27,24 +27,24 @@ import logging from botocore import xform_name - logger = logging.getLogger(__name__) -class Identifier(object): +class Identifier: """ A resource identifier, given by its name. :type name: string :param name: The name of the identifier """ + def __init__(self, name, member_name=None): #: (``string``) The name of the identifier self.name = name self.member_name = member_name -class Action(object): +class Action: """ A service operation action. @@ -55,6 +55,7 @@ class Action(object): :type resource_defs: dict :param resource_defs: All resources defined in the service """ + def __init__(self, name, definition, resource_defs): self._definition = definition @@ -67,13 +68,14 @@ class Action(object): #: (:py:class:`ResponseResource`) This action's resource or ``None`` self.resource = None if 'resource' in definition: - self.resource = ResponseResource(definition.get('resource', {}), - resource_defs) + self.resource = ResponseResource( + definition.get('resource', {}), resource_defs + ) #: (``string``) The JMESPath search path or ``None`` self.path = definition.get('path') -class DefinitionWithParams(object): +class DefinitionWithParams: """ An item which has parameters exposed via the ``params`` property. A request has an operation and parameters, while a waiter has @@ -82,6 +84,7 @@ class DefinitionWithParams(object): :type definition: dict :param definition: The JSON definition """ + def __init__(self, definition): self._definition = definition @@ -100,7 +103,7 @@ class DefinitionWithParams(object): return params -class Parameter(object): +class Parameter: """ An auto-filled parameter which has a source and target. For example, the ``QueueUrl`` may be auto-filled from a resource's ``url`` identifier @@ -113,8 +116,10 @@ class Parameter(object): :type source: string :param source: The source name, e.g. ``Url`` """ - def __init__(self, target, source, name=None, path=None, value=None, - **kwargs): + + def __init__( + self, target, source, name=None, path=None, value=None, **kwargs + ): #: (``string``) The destination parameter name self.target = target #: (``string``) Where the source is defined @@ -138,8 +143,9 @@ class Request(DefinitionWithParams): :type definition: dict :param definition: The JSON definition """ + def __init__(self, definition): - super(Request, self).__init__(definition) + super().__init__(definition) #: (``string``) The name of the low-level service operation self.operation = definition.get('operation') @@ -154,10 +160,11 @@ class Waiter(DefinitionWithParams): :type definition: dict :param definition: The JSON definition """ + PREFIX = 'WaitUntil' def __init__(self, name, definition): - super(Waiter, self).__init__(definition) + super().__init__(definition) #: (``string``) The name of this waiter self.name = name @@ -166,7 +173,7 @@ class Waiter(DefinitionWithParams): self.waiter_name = definition.get('waiterName') -class ResponseResource(object): +class ResponseResource: """ A resource response to create after performing an action. @@ -175,6 +182,7 @@ class ResponseResource(object): :type resource_defs: dict :param resource_defs: All resources defined in the service """ + def __init__(self, definition, resource_defs): self._definition = definition self._resource_defs = resource_defs @@ -195,8 +203,7 @@ class ResponseResource(object): identifiers = [] for item in self._definition.get('identifiers', []): - identifiers.append( - Parameter(**item)) + identifiers.append(Parameter(**item)) return identifiers @@ -207,8 +214,9 @@ class ResponseResource(object): :type: :py:class:`ResourceModel` """ - return ResourceModel(self.type, self._resource_defs[self.type], - self._resource_defs) + return ResourceModel( + self.type, self._resource_defs[self.type], self._resource_defs + ) class Collection(Action): @@ -222,6 +230,7 @@ class Collection(Action): :type resource_defs: dict :param resource_defs: All resources defined in the service """ + @property def batch_actions(self): """ @@ -234,7 +243,7 @@ class Collection(Action): return self.resource.model.batch_actions -class ResourceModel(object): +class ResourceModel: """ A model representing a resource, defined via a JSON description format. A resource has identifiers, attributes, actions, @@ -248,6 +257,7 @@ class ResourceModel(object): :type resource_defs: dict :param resource_defs: All resources defined in the service """ + def __init__(self, name, definition, resource_defs): self._definition = definition self._resource_defs = resource_defs @@ -296,7 +306,7 @@ class ResourceModel(object): :param shape: The underlying shape for this resource. """ # Meta is a reserved name for resources - names = set(['meta']) + names = {'meta'} self._renamed = {} if self._definition.get('load'): @@ -318,8 +328,9 @@ class ResourceModel(object): break if not data_required: - self._load_name_with_category(names, name, 'subresource', - snake_case=False) + self._load_name_with_category( + names, name, 'subresource', snake_case=False + ) else: self._load_name_with_category(names, name, 'reference') @@ -327,15 +338,15 @@ class ResourceModel(object): self._load_name_with_category(names, name, 'collection') for name in self._definition.get('waiters', {}): - self._load_name_with_category(names, Waiter.PREFIX + name, - 'waiter') + self._load_name_with_category( + names, Waiter.PREFIX + name, 'waiter' + ) if shape is not None: for name in shape.members.keys(): self._load_name_with_category(names, name, 'attribute') - def _load_name_with_category(self, names, name, category, - snake_case=True): + def _load_name_with_category(self, names, name, category, snake_case=True): """ Load a name with a given category, possibly renaming it if that name is already in use. The name will be stored @@ -355,15 +366,18 @@ class ResourceModel(object): name = xform_name(name) if name in names: - logger.debug('Renaming %s %s %s' % (self.name, category, name)) + logger.debug(f'Renaming {self.name} {category} {name}') self._renamed[(category, name)] = name + '_' + category name += '_' + category if name in names: # This isn't good, let's raise instead of trying to keep # renaming this value. - raise ValueError('Problem renaming {0} {1} to {2}!'.format( - self.name, category, name)) + raise ValueError( + 'Problem renaming {} {} to {}!'.format( + self.name, category, name + ) + ) names.add(name) @@ -411,8 +425,9 @@ class ResourceModel(object): if snake_cased in identifier_names: # Skip identifiers, these are set through other means continue - snake_cased = self._get_name('attribute', snake_cased, - snake_case=False) + snake_cased = self._get_name( + 'attribute', snake_cased, snake_case=False + ) attributes[snake_cased] = (name, member) return attributes @@ -524,17 +539,12 @@ class ResourceModel(object): # } # } # - fake_has = { - 'resource': { - 'type': name, - 'identifiers': [] - } - } + fake_has = {'resource': {'type': name, 'identifiers': []}} for identifier in resource_def.get('identifiers', []): - fake_has['resource']['identifiers'].append({ - 'target': identifier['name'], 'source': 'input' - }) + fake_has['resource']['identifiers'].append( + {'target': identifier['name'], 'source': 'input'} + ) definition[name] = fake_has else: diff --git a/boto3/resources/params.py b/boto3/resources/params.py index 6db52ab..3c5c74b 100644 --- a/boto3/resources/params.py +++ b/boto3/resources/params.py @@ -18,7 +18,6 @@ from botocore import xform_name from ..exceptions import ResourceLoadException - INDEX_RE = re.compile(r'\[(.*)\]$') @@ -43,7 +42,8 @@ def get_data_member(parent, path): parent.load() else: raise ResourceLoadException( - '{0} has no load method!'.format(parent.__class__.__name__)) + f'{parent.__class__.__name__} has no load method!' + ) return jmespath.search(path, parent.meta.data) @@ -90,8 +90,7 @@ def create_request_parameters(parent, request_model, params=None, index=None): # This is provided by the user, so ignore it here continue else: - raise NotImplementedError( - 'Unsupported source type: {0}'.format(source)) + raise NotImplementedError(f'Unsupported source type: {source}') build_param_structure(params, target, value, index) @@ -133,7 +132,7 @@ def build_param_structure(params, target, value, index=None): else: # We have an explicit index index = int(result.group(1)) - part = part[:-len(str(index) + '[]')] + part = part[: -len(str(index) + '[]')] else: # Index will be set after we know the proper part # name and that it's a list instance. diff --git a/boto3/resources/response.py b/boto3/resources/response.py index c37d3ae..6dd92ac 100644 --- a/boto3/resources/response.py +++ b/boto3/resources/response.py @@ -68,8 +68,7 @@ def build_identifiers(identifiers, parent, params=None, raw_response=None): # This value is set by the user, so ignore it here continue else: - raise NotImplementedError( - 'Unsupported source type: {0}'.format(source)) + raise NotImplementedError(f'Unsupported source type: {source}') results.append((xform_name(target), value)) @@ -111,8 +110,10 @@ def build_empty_response(search_path, operation_name, service_model): shape = shape.member else: raise NotImplementedError( - 'Search path hits shape type {0} from {1}'.format( - shape.type_name, item)) + 'Search path hits shape type {} from {}'.format( + shape.type_name, item + ) + ) # Anything not handled here is set to None if shape.type_name == 'structure': @@ -125,7 +126,7 @@ def build_empty_response(search_path, operation_name, service_model): return response -class RawHandler(object): +class RawHandler: """ A raw action response handler. This passed through the response dictionary, optionally after performing a JMESPath search if one @@ -136,6 +137,7 @@ class RawHandler(object): :rtype: dict :return: Service response """ + def __init__(self, search_path): self.search_path = search_path @@ -155,7 +157,7 @@ class RawHandler(object): return response -class ResourceHandler(object): +class ResourceHandler: """ Creates a new resource or list of new resources from the low-level response based on the given response resource definition. @@ -180,8 +182,15 @@ class ResourceHandler(object): :rtype: ServiceResource or list :return: New resource instance(s). """ - def __init__(self, search_path, factory, resource_model, - service_context, operation_name=None): + + def __init__( + self, + search_path, + factory, + resource_model, + service_context, + operation_name=None, + ): self.search_path = search_path self.factory = factory self.resource_model = resource_model @@ -199,13 +208,14 @@ class ResourceHandler(object): """ resource_name = self.resource_model.type json_definition = self.service_context.resource_json_definitions.get( - resource_name) + resource_name + ) # Load the new resource class that will result from this action. resource_cls = self.factory.load_from_definition( resource_name=resource_name, single_resource_json_definition=json_definition, - service_context=self.service_context + service_context=self.service_context, ) raw_response = response search_response = None @@ -222,9 +232,11 @@ class ResourceHandler(object): # will have one item consumed from the front of the list for each # resource that is instantiated. Items which are not a list will # be set as the same value on each new resource instance. - identifiers = dict(build_identifiers( - self.resource_model.identifiers, parent, params, - raw_response)) + identifiers = dict( + build_identifiers( + self.resource_model.identifiers, parent, params, raw_response + ) + ) # If any of the identifiers is a list, then the response is plural plural = [v for v in identifiers.values() if isinstance(v, list)] @@ -242,13 +254,16 @@ class ResourceHandler(object): if search_response: response_item = search_response[i] response.append( - self.handle_response_item(resource_cls, parent, - identifiers, response_item)) + self.handle_response_item( + resource_cls, parent, identifiers, response_item + ) + ) elif all_not_none(identifiers.values()): # All identifiers must always exist, otherwise the resource # cannot be instantiated. response = self.handle_response_item( - resource_cls, parent, identifiers, search_response) + resource_cls, parent, identifiers, search_response + ) else: # The response should be empty, but that may mean an # empty dict, list, or None based on whether we make @@ -259,13 +274,16 @@ class ResourceHandler(object): # A remote service call was made, so try and determine # its shape. response = build_empty_response( - self.search_path, self.operation_name, - self.service_context.service_model) + self.search_path, + self.operation_name, + self.service_context.service_model, + ) return response - def handle_response_item(self, resource_cls, parent, identifiers, - resource_data): + def handle_response_item( + self, resource_cls, parent, identifiers, resource_data + ): """ Handles the creation of a single response item by setting parameters and creating the appropriate resource instance. diff --git a/boto3/s3/inject.py b/boto3/s3/inject.py index 4f2a8f6..d57e308 100644 --- a/boto3/s3/inject.py +++ b/boto3/s3/inject.py @@ -12,10 +12,13 @@ # language governing permissions and limitations under the License. from botocore.exceptions import ClientError -from boto3.s3.transfer import create_transfer_manager -from boto3.s3.transfer import TransferConfig, S3Transfer -from boto3.s3.transfer import ProgressCallbackInvoker from boto3 import utils +from boto3.s3.transfer import ( + ProgressCallbackInvoker, + S3Transfer, + TransferConfig, + create_transfer_manager, +) def inject_s3_transfer_methods(class_attributes, **kwargs): @@ -24,30 +27,37 @@ def inject_s3_transfer_methods(class_attributes, **kwargs): utils.inject_attribute(class_attributes, 'copy', copy) utils.inject_attribute(class_attributes, 'upload_fileobj', upload_fileobj) utils.inject_attribute( - class_attributes, 'download_fileobj', download_fileobj) + class_attributes, 'download_fileobj', download_fileobj + ) def inject_bucket_methods(class_attributes, **kwargs): utils.inject_attribute(class_attributes, 'load', bucket_load) utils.inject_attribute(class_attributes, 'upload_file', bucket_upload_file) utils.inject_attribute( - class_attributes, 'download_file', bucket_download_file) + class_attributes, 'download_file', bucket_download_file + ) utils.inject_attribute(class_attributes, 'copy', bucket_copy) utils.inject_attribute( - class_attributes, 'upload_fileobj', bucket_upload_fileobj) + class_attributes, 'upload_fileobj', bucket_upload_fileobj + ) utils.inject_attribute( - class_attributes, 'download_fileobj', bucket_download_fileobj) + class_attributes, 'download_fileobj', bucket_download_fileobj + ) def inject_object_methods(class_attributes, **kwargs): utils.inject_attribute(class_attributes, 'upload_file', object_upload_file) utils.inject_attribute( - class_attributes, 'download_file', object_download_file) + class_attributes, 'download_file', object_download_file + ) utils.inject_attribute(class_attributes, 'copy', object_copy) utils.inject_attribute( - class_attributes, 'upload_fileobj', object_upload_fileobj) + class_attributes, 'upload_fileobj', object_upload_fileobj + ) utils.inject_attribute( - class_attributes, 'download_fileobj', object_download_fileobj) + class_attributes, 'download_fileobj', object_download_fileobj + ) def inject_object_summary_methods(class_attributes, **kwargs): @@ -85,14 +95,16 @@ def object_summary_load(self, *args, **kwargs): resource. """ response = self.meta.client.head_object( - Bucket=self.bucket_name, Key=self.key) + Bucket=self.bucket_name, Key=self.key + ) if 'ContentLength' in response: response['Size'] = response.pop('ContentLength') self.meta.data = response -def upload_file(self, Filename, Bucket, Key, ExtraArgs=None, - Callback=None, Config=None): +def upload_file( + self, Filename, Bucket, Key, ExtraArgs=None, Callback=None, Config=None +): """Upload a file to an S3 object. Usage:: @@ -116,7 +128,8 @@ def upload_file(self, Filename, Bucket, Key, ExtraArgs=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed upload arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -128,12 +141,17 @@ def upload_file(self, Filename, Bucket, Key, ExtraArgs=None, """ with S3Transfer(self, Config) as transfer: return transfer.upload_file( - filename=Filename, bucket=Bucket, key=Key, - extra_args=ExtraArgs, callback=Callback) + filename=Filename, + bucket=Bucket, + key=Key, + extra_args=ExtraArgs, + callback=Callback, + ) -def download_file(self, Bucket, Key, Filename, ExtraArgs=None, - Callback=None, Config=None): +def download_file( + self, Bucket, Key, Filename, ExtraArgs=None, Callback=None, Config=None +): """Download an S3 object to a file. Usage:: @@ -157,7 +175,8 @@ def download_file(self, Bucket, Key, Filename, ExtraArgs=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -169,12 +188,17 @@ def download_file(self, Bucket, Key, Filename, ExtraArgs=None, """ with S3Transfer(self, Config) as transfer: return transfer.download_file( - bucket=Bucket, key=Key, filename=Filename, - extra_args=ExtraArgs, callback=Callback) + bucket=Bucket, + key=Key, + filename=Filename, + extra_args=ExtraArgs, + callback=Callback, + ) -def bucket_upload_file(self, Filename, Key, - ExtraArgs=None, Callback=None, Config=None): +def bucket_upload_file( + self, Filename, Key, ExtraArgs=None, Callback=None, Config=None +): """Upload a file to an S3 object. Usage:: @@ -195,7 +219,8 @@ def bucket_upload_file(self, Filename, Key, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed upload arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -206,12 +231,18 @@ def bucket_upload_file(self, Filename, Key, transfer. """ return self.meta.client.upload_file( - Filename=Filename, Bucket=self.name, Key=Key, - ExtraArgs=ExtraArgs, Callback=Callback, Config=Config) + Filename=Filename, + Bucket=self.name, + Key=Key, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def bucket_download_file(self, Key, Filename, - ExtraArgs=None, Callback=None, Config=None): +def bucket_download_file( + self, Key, Filename, ExtraArgs=None, Callback=None, Config=None +): """Download an S3 object to a file. Usage:: @@ -232,7 +263,8 @@ def bucket_download_file(self, Key, Filename, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -243,12 +275,18 @@ def bucket_download_file(self, Key, Filename, transfer. """ return self.meta.client.download_file( - Bucket=self.name, Key=Key, Filename=Filename, - ExtraArgs=ExtraArgs, Callback=Callback, Config=Config) + Bucket=self.name, + Key=Key, + Filename=Filename, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def object_upload_file(self, Filename, - ExtraArgs=None, Callback=None, Config=None): +def object_upload_file( + self, Filename, ExtraArgs=None, Callback=None, Config=None +): """Upload a file to an S3 object. Usage:: @@ -266,7 +304,8 @@ def object_upload_file(self, Filename, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed upload arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -277,12 +316,18 @@ def object_upload_file(self, Filename, transfer. """ return self.meta.client.upload_file( - Filename=Filename, Bucket=self.bucket_name, Key=self.key, - ExtraArgs=ExtraArgs, Callback=Callback, Config=Config) + Filename=Filename, + Bucket=self.bucket_name, + Key=self.key, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def object_download_file(self, Filename, - ExtraArgs=None, Callback=None, Config=None): +def object_download_file( + self, Filename, ExtraArgs=None, Callback=None, Config=None +): """Download an S3 object to a file. Usage:: @@ -300,7 +345,8 @@ def object_download_file(self, Filename, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -311,12 +357,25 @@ def object_download_file(self, Filename, transfer. """ return self.meta.client.download_file( - Bucket=self.bucket_name, Key=self.key, Filename=Filename, - ExtraArgs=ExtraArgs, Callback=Callback, Config=Config) + Bucket=self.bucket_name, + Key=self.key, + Filename=Filename, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def copy(self, CopySource, Bucket, Key, ExtraArgs=None, Callback=None, - SourceClient=None, Config=None): +def copy( + self, + CopySource, + Bucket, + Key, + ExtraArgs=None, + Callback=None, + SourceClient=None, + Config=None, +): """Copy an object from one S3 location to another. This is a managed transfer which will perform a multipart copy in @@ -347,7 +406,8 @@ def copy(self, CopySource, Bucket, Key, ExtraArgs=None, Callback=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -374,14 +434,25 @@ def copy(self, CopySource, Bucket, Key, ExtraArgs=None, Callback=None, with create_transfer_manager(self, config) as manager: future = manager.copy( - copy_source=CopySource, bucket=Bucket, key=Key, - extra_args=ExtraArgs, subscribers=subscribers, - source_client=SourceClient) + copy_source=CopySource, + bucket=Bucket, + key=Key, + extra_args=ExtraArgs, + subscribers=subscribers, + source_client=SourceClient, + ) return future.result() -def bucket_copy(self, CopySource, Key, ExtraArgs=None, Callback=None, - SourceClient=None, Config=None): +def bucket_copy( + self, + CopySource, + Key, + ExtraArgs=None, + Callback=None, + SourceClient=None, + Config=None, +): """Copy an object from one S3 location to an object in this bucket. This is a managed transfer which will perform a multipart copy in @@ -410,7 +481,8 @@ def bucket_copy(self, CopySource, Key, ExtraArgs=None, Callback=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -428,12 +500,24 @@ def bucket_copy(self, CopySource, Key, ExtraArgs=None, Callback=None, copy. """ return self.meta.client.copy( - CopySource=CopySource, Bucket=self.name, Key=Key, ExtraArgs=ExtraArgs, - Callback=Callback, SourceClient=SourceClient, Config=Config) + CopySource=CopySource, + Bucket=self.name, + Key=Key, + ExtraArgs=ExtraArgs, + Callback=Callback, + SourceClient=SourceClient, + Config=Config, + ) -def object_copy(self, CopySource, ExtraArgs=None, Callback=None, - SourceClient=None, Config=None): +def object_copy( + self, + CopySource, + ExtraArgs=None, + Callback=None, + SourceClient=None, + Config=None, +): """Copy an object from one S3 location to this object. This is a managed transfer which will perform a multipart copy in @@ -460,7 +544,8 @@ def object_copy(self, CopySource, ExtraArgs=None, Callback=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -478,13 +563,19 @@ def object_copy(self, CopySource, ExtraArgs=None, Callback=None, copy. """ return self.meta.client.copy( - CopySource=CopySource, Bucket=self.bucket_name, Key=self.key, - ExtraArgs=ExtraArgs, Callback=Callback, SourceClient=SourceClient, - Config=Config) + CopySource=CopySource, + Bucket=self.bucket_name, + Key=self.key, + ExtraArgs=ExtraArgs, + Callback=Callback, + SourceClient=SourceClient, + Config=Config, + ) -def upload_fileobj(self, Fileobj, Bucket, Key, ExtraArgs=None, - Callback=None, Config=None): +def upload_fileobj( + self, Fileobj, Bucket, Key, ExtraArgs=None, Callback=None, Config=None +): """Upload a file-like object to S3. The file-like object must be in binary mode. @@ -512,7 +603,8 @@ def upload_fileobj(self, Fileobj, Bucket, Key, ExtraArgs=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed upload arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -535,13 +627,18 @@ def upload_fileobj(self, Fileobj, Bucket, Key, ExtraArgs=None, with create_transfer_manager(self, config) as manager: future = manager.upload( - fileobj=Fileobj, bucket=Bucket, key=Key, - extra_args=ExtraArgs, subscribers=subscribers) + fileobj=Fileobj, + bucket=Bucket, + key=Key, + extra_args=ExtraArgs, + subscribers=subscribers, + ) return future.result() -def bucket_upload_fileobj(self, Fileobj, Key, ExtraArgs=None, - Callback=None, Config=None): +def bucket_upload_fileobj( + self, Fileobj, Key, ExtraArgs=None, Callback=None, Config=None +): """Upload a file-like object to this bucket. The file-like object must be in binary mode. @@ -567,7 +664,8 @@ def bucket_upload_fileobj(self, Fileobj, Key, ExtraArgs=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed upload arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -578,12 +676,18 @@ def bucket_upload_fileobj(self, Fileobj, Key, ExtraArgs=None, upload. """ return self.meta.client.upload_fileobj( - Fileobj=Fileobj, Bucket=self.name, Key=Key, ExtraArgs=ExtraArgs, - Callback=Callback, Config=Config) + Fileobj=Fileobj, + Bucket=self.name, + Key=Key, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def object_upload_fileobj(self, Fileobj, ExtraArgs=None, Callback=None, - Config=None): +def object_upload_fileobj( + self, Fileobj, ExtraArgs=None, Callback=None, Config=None +): """Upload a file-like object to this object. The file-like object must be in binary mode. @@ -607,7 +711,8 @@ def object_upload_fileobj(self, Fileobj, ExtraArgs=None, Callback=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed upload arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_UPLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -618,12 +723,18 @@ def object_upload_fileobj(self, Fileobj, ExtraArgs=None, Callback=None, upload. """ return self.meta.client.upload_fileobj( - Fileobj=Fileobj, Bucket=self.bucket_name, Key=self.key, - ExtraArgs=ExtraArgs, Callback=Callback, Config=Config) + Fileobj=Fileobj, + Bucket=self.bucket_name, + Key=self.key, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def download_fileobj(self, Bucket, Key, Fileobj, ExtraArgs=None, - Callback=None, Config=None): +def download_fileobj( + self, Bucket, Key, Fileobj, ExtraArgs=None, Callback=None, Config=None +): """Download an object from S3 to a file-like object. The file-like object must be in binary mode. @@ -651,7 +762,8 @@ def download_fileobj(self, Bucket, Key, Fileobj, ExtraArgs=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -674,13 +786,18 @@ def download_fileobj(self, Bucket, Key, Fileobj, ExtraArgs=None, with create_transfer_manager(self, config) as manager: future = manager.download( - bucket=Bucket, key=Key, fileobj=Fileobj, - extra_args=ExtraArgs, subscribers=subscribers) + bucket=Bucket, + key=Key, + fileobj=Fileobj, + extra_args=ExtraArgs, + subscribers=subscribers, + ) return future.result() -def bucket_download_fileobj(self, Key, Fileobj, ExtraArgs=None, - Callback=None, Config=None): +def bucket_download_fileobj( + self, Key, Fileobj, ExtraArgs=None, Callback=None, Config=None +): """Download an object from this bucket to a file-like-object. The file-like object must be in binary mode. @@ -706,7 +823,8 @@ def bucket_download_fileobj(self, Key, Fileobj, ExtraArgs=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -717,12 +835,18 @@ def bucket_download_fileobj(self, Key, Fileobj, ExtraArgs=None, download. """ return self.meta.client.download_fileobj( - Bucket=self.name, Key=Key, Fileobj=Fileobj, ExtraArgs=ExtraArgs, - Callback=Callback, Config=Config) + Bucket=self.name, + Key=Key, + Fileobj=Fileobj, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) -def object_download_fileobj(self, Fileobj, ExtraArgs=None, Callback=None, - Config=None): +def object_download_fileobj( + self, Fileobj, ExtraArgs=None, Callback=None, Config=None +): """Download this object from S3 to a file-like object. The file-like object must be in binary mode. @@ -746,7 +870,8 @@ def object_download_fileobj(self, Fileobj, ExtraArgs=None, Callback=None, :type ExtraArgs: dict :param ExtraArgs: Extra arguments that may be passed to the - client operation. + client operation. For allowed download arguments see + boto3.s3.transfer.S3Transfer.ALLOWED_DOWNLOAD_ARGS. :type Callback: function :param Callback: A method which takes a number of bytes transferred to @@ -757,5 +882,10 @@ def object_download_fileobj(self, Fileobj, ExtraArgs=None, Callback=None, download. """ return self.meta.client.download_fileobj( - Bucket=self.bucket_name, Key=self.key, Fileobj=Fileobj, - ExtraArgs=ExtraArgs, Callback=Callback, Config=Config) + Bucket=self.bucket_name, + Key=self.key, + Fileobj=Fileobj, + ExtraArgs=ExtraArgs, + Callback=Callback, + Config=Config, + ) diff --git a/boto3/s3/transfer.py b/boto3/s3/transfer.py index 79d4487..cd7210e 100644 --- a/boto3/s3/transfer.py +++ b/boto3/s3/transfer.py @@ -123,18 +123,17 @@ transfer. For example: """ from botocore.exceptions import ClientError -from botocore.compat import six -from s3transfer.exceptions import RetriesExceededError as \ - S3TransferRetriesExceededError +from s3transfer.exceptions import ( + RetriesExceededError as S3TransferRetriesExceededError, +) +from s3transfer.futures import NonThreadedExecutor from s3transfer.manager import TransferConfig as S3TransferConfig from s3transfer.manager import TransferManager -from s3transfer.futures import NonThreadedExecutor from s3transfer.subscribers import BaseSubscriber from s3transfer.utils import OSUtils from boto3.exceptions import RetriesExceededError, S3UploadFailedError - KB = 1024 MB = KB * KB @@ -163,7 +162,7 @@ def create_transfer_manager(client, config, osutil=None): class TransferConfig(S3TransferConfig): ALIAS = { 'max_concurrency': 'max_request_concurrency', - 'max_io_queue': 'max_io_queue_size' + 'max_io_queue': 'max_io_queue_size', } def __init__( @@ -217,7 +216,7 @@ class TransferConfig(S3TransferConfig): in uploading and downloading file content. The value is an integer in terms of bytes per second. """ - super(TransferConfig, self).__init__( + super().__init__( multipart_threshold=multipart_threshold, max_request_concurrency=max_concurrency, multipart_chunksize=multipart_chunksize, @@ -237,12 +236,12 @@ class TransferConfig(S3TransferConfig): # If the alias name is used, make sure we set the name that it points # to as that is what actually is used in governing the TransferManager. if name in self.ALIAS: - super(TransferConfig, self).__setattr__(self.ALIAS[name], value) + super().__setattr__(self.ALIAS[name], value) # Always set the value of the actual name provided. - super(TransferConfig, self).__setattr__(name, value) + super().__setattr__(name, value) -class S3Transfer(object): +class S3Transfer: ALLOWED_DOWNLOAD_ARGS = TransferManager.ALLOWED_DOWNLOAD_ARGS ALLOWED_UPLOAD_ARGS = TransferManager.ALLOWED_UPLOAD_ARGS @@ -266,8 +265,9 @@ class S3Transfer(object): else: self._manager = create_transfer_manager(client, config, osutil) - def upload_file(self, filename, bucket, key, - callback=None, extra_args=None): + def upload_file( + self, filename, bucket, key, callback=None, extra_args=None + ): """Upload a file to an S3 object. Variants have also been injected into S3 client, Bucket and Object. @@ -277,12 +277,13 @@ class S3Transfer(object): :py:meth:`S3.Client.upload_file` :py:meth:`S3.Client.upload_fileobj` """ - if not isinstance(filename, six.string_types): + if not isinstance(filename, str): raise ValueError('Filename must be a string') subscribers = self._get_subscribers(callback) future = self._manager.upload( - filename, bucket, key, extra_args, subscribers) + filename, bucket, key, extra_args, subscribers + ) try: future.result() # If a client error was raised, add the backwards compatibility layer @@ -291,11 +292,14 @@ class S3Transfer(object): # client error. except ClientError as e: raise S3UploadFailedError( - "Failed to upload %s to %s: %s" % ( - filename, '/'.join([bucket, key]), e)) + "Failed to upload {} to {}: {}".format( + filename, '/'.join([bucket, key]), e + ) + ) - def download_file(self, bucket, key, filename, extra_args=None, - callback=None): + def download_file( + self, bucket, key, filename, extra_args=None, callback=None + ): """Download an S3 object to a file. Variants have also been injected into S3 client, Bucket and Object. @@ -305,12 +309,13 @@ class S3Transfer(object): :py:meth:`S3.Client.download_file` :py:meth:`S3.Client.download_fileobj` """ - if not isinstance(filename, six.string_types): + if not isinstance(filename, str): raise ValueError('Filename must be a string') subscribers = self._get_subscribers(callback) future = self._manager.download( - bucket, key, filename, extra_args, subscribers) + bucket, key, filename, extra_args, subscribers + ) try: future.result() # This is for backwards compatibility where when retries are @@ -339,6 +344,7 @@ class ProgressCallbackInvoker(BaseSubscriber): :param callback: A callable that takes a single positional argument for how many bytes were transferred. """ + def __init__(self, callback): self._callback = callback diff --git a/boto3/session.py b/boto3/session.py index 3a3a654..bdda65a 100644 --- a/boto3/session.py +++ b/boto3/session.py @@ -25,7 +25,7 @@ from boto3.exceptions import ResourceNotExistsError, UnknownAPIVersionError from .resources.factory import ResourceFactory -class Session(object): +class Session: """ A session stores configuration state and allows you to create service clients and resources. @@ -45,9 +45,16 @@ class Session(object): :param profile_name: The name of a profile to use. If not given, then the default profile is used. """ - def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, - aws_session_token=None, region_name=None, - botocore_session=None, profile_name=None): + + def __init__( + self, + aws_access_key_id=None, + aws_secret_access_key=None, + aws_session_token=None, + region_name=None, + botocore_session=None, + profile_name=None, + ): if botocore_session is not None: self._session = botocore_session else: @@ -56,8 +63,9 @@ class Session(object): # Setup custom user-agent string if it isn't already customized if self._session.user_agent_name == 'Botocore': - botocore_info = 'Botocore/{0}'.format( - self._session.user_agent_version) + botocore_info = 'Botocore/{}'.format( + self._session.user_agent_version + ) if self._session.user_agent_extra: self._session.user_agent_extra += ' ' + botocore_info else: @@ -70,20 +78,23 @@ class Session(object): if aws_access_key_id or aws_secret_access_key or aws_session_token: self._session.set_credentials( - aws_access_key_id, aws_secret_access_key, aws_session_token) + aws_access_key_id, aws_secret_access_key, aws_session_token + ) if region_name is not None: self._session.set_config_variable('region', region_name) self.resource_factory = ResourceFactory( - self._session.get_component('event_emitter')) + self._session.get_component('event_emitter') + ) self._setup_loader() self._register_default_handlers() def __repr__(self): - return '{0}(region_name={1})'.format( + return '{}(region_name={})'.format( self.__class__.__name__, - repr(self._session.get_config_variable('region'))) + repr(self._session.get_config_variable('region')), + ) @property def profile_name(self): @@ -119,7 +130,8 @@ class Session(object): """ self._loader = self._session.get_component('data_loader') self._loader.search_paths.append( - os.path.join(os.path.dirname(__file__), 'data')) + os.path.join(os.path.dirname(__file__), 'data') + ) def get_available_services(self): """ @@ -149,10 +161,16 @@ class Session(object): """ return self._session.get_available_partitions() - def get_available_regions(self, service_name, partition_name='aws', - allow_non_regional=False): + def get_available_regions( + self, service_name, partition_name='aws', allow_non_regional=False + ): """Lists the region and endpoint names of a particular partition. + The list of regions returned by this method are regions that are + explicitly known by the client to exist and is not comprehensive. A + region not returned in this list may still be available for the + provided service. + :type service_name: string :param service_name: Name of a service to list endpoint for (e.g., s3). @@ -169,12 +187,14 @@ class Session(object): :return: Returns a list of endpoint names (e.g., ["us-east-1"]). """ return self._session.get_available_regions( - service_name=service_name, partition_name=partition_name, - allow_non_regional=allow_non_regional) + service_name=service_name, + partition_name=partition_name, + allow_non_regional=allow_non_regional, + ) def get_credentials(self): """ - Return the :class:`botocore.credential.Credential` object + Return the :class:`botocore.credentials.Credentials` object associated with this session. If the credentials have not yet been loaded, this will attempt to load them. If they have already been loaded, this will return the cached @@ -194,10 +214,19 @@ class Session(object): """ return self._session.get_partition_for_region(region_name) - def client(self, service_name, region_name=None, api_version=None, - use_ssl=True, verify=None, endpoint_url=None, - aws_access_key_id=None, aws_secret_access_key=None, - aws_session_token=None, config=None): + def client( + self, + service_name, + region_name=None, + api_version=None, + use_ssl=True, + verify=None, + endpoint_url=None, + aws_access_key_id=None, + aws_secret_access_key=None, + aws_session_token=None, + config=None, + ): """ Create a low-level service client by name. @@ -268,16 +297,31 @@ class Session(object): """ return self._session.create_client( - service_name, region_name=region_name, api_version=api_version, - use_ssl=use_ssl, verify=verify, endpoint_url=endpoint_url, + service_name, + region_name=region_name, + api_version=api_version, + use_ssl=use_ssl, + verify=verify, + endpoint_url=endpoint_url, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, config=config) + aws_session_token=aws_session_token, + config=config, + ) - def resource(self, service_name, region_name=None, api_version=None, - use_ssl=True, verify=None, endpoint_url=None, - aws_access_key_id=None, aws_secret_access_key=None, - aws_session_token=None, config=None): + def resource( + self, + service_name, + region_name=None, + api_version=None, + use_ssl=True, + verify=None, + endpoint_url=None, + aws_access_key_id=None, + aws_secret_access_key=None, + aws_session_token=None, + config=None, + ): """ Create a resource service client by name. @@ -350,19 +394,24 @@ class Session(object): """ try: resource_model = self._loader.load_service_model( - service_name, 'resources-1', api_version) + service_name, 'resources-1', api_version + ) except UnknownServiceError: available = self.get_available_resources() has_low_level_client = ( - service_name in self.get_available_services()) - raise ResourceNotExistsError(service_name, available, - has_low_level_client) + service_name in self.get_available_services() + ) + raise ResourceNotExistsError( + service_name, available, has_low_level_client + ) except DataNotFoundError: # This is because we've provided an invalid API version. available_api_versions = self._loader.list_api_versions( - service_name, 'resources-1') + service_name, 'resources-1' + ) raise UnknownAPIVersionError( - service_name, api_version, ', '.join(available_api_versions)) + service_name, api_version, ', '.join(available_api_versions) + ) if api_version is None: # Even though botocore's load_service_model() can handle @@ -381,7 +430,8 @@ class Session(object): # and loader.determine_latest_version(..., 'resources-1') # both load the same api version of the file. api_version = self._loader.determine_latest_version( - service_name, 'resources-1') + service_name, 'resources-1' + ) # Creating a new resource instance requires the low-level client # and service model, the resource version and resource JSON data. @@ -394,28 +444,35 @@ class Session(object): else: config = Config(user_agent_extra='Resource') client = self.client( - service_name, region_name=region_name, api_version=api_version, - use_ssl=use_ssl, verify=verify, endpoint_url=endpoint_url, + service_name, + region_name=region_name, + api_version=api_version, + use_ssl=use_ssl, + verify=verify, + endpoint_url=endpoint_url, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, - aws_session_token=aws_session_token, config=config) + aws_session_token=aws_session_token, + config=config, + ) service_model = client.meta.service_model # Create a ServiceContext object to serve as a reference to # important read-only information about the general service. service_context = boto3.utils.ServiceContext( - service_name=service_name, service_model=service_model, + service_name=service_name, + service_model=service_model, resource_json_definitions=resource_model['resources'], service_waiter_model=boto3.utils.LazyLoadedWaiterModel( self._session, service_name, api_version - ) + ), ) # Create the service resource class. cls = self.resource_factory.load_from_definition( resource_name=service_name, single_resource_json_definition=resource_model['service'], - service_context=service_context + service_context=service_context, ) return cls(client=client) @@ -426,40 +483,50 @@ class Session(object): self._session.register( 'creating-client-class.s3', boto3.utils.lazy_call( - 'boto3.s3.inject.inject_s3_transfer_methods')) + 'boto3.s3.inject.inject_s3_transfer_methods' + ), + ) self._session.register( 'creating-resource-class.s3.Bucket', - boto3.utils.lazy_call( - 'boto3.s3.inject.inject_bucket_methods')) + boto3.utils.lazy_call('boto3.s3.inject.inject_bucket_methods'), + ) self._session.register( 'creating-resource-class.s3.Object', - boto3.utils.lazy_call( - 'boto3.s3.inject.inject_object_methods')) + boto3.utils.lazy_call('boto3.s3.inject.inject_object_methods'), + ) self._session.register( 'creating-resource-class.s3.ObjectSummary', boto3.utils.lazy_call( - 'boto3.s3.inject.inject_object_summary_methods')) + 'boto3.s3.inject.inject_object_summary_methods' + ), + ) # DynamoDb customizations self._session.register( 'creating-resource-class.dynamodb', boto3.utils.lazy_call( - 'boto3.dynamodb.transform.register_high_level_interface'), - unique_id='high-level-dynamodb') + 'boto3.dynamodb.transform.register_high_level_interface' + ), + unique_id='high-level-dynamodb', + ) self._session.register( 'creating-resource-class.dynamodb.Table', boto3.utils.lazy_call( - 'boto3.dynamodb.table.register_table_methods'), - unique_id='high-level-dynamodb-table') + 'boto3.dynamodb.table.register_table_methods' + ), + unique_id='high-level-dynamodb-table', + ) # EC2 Customizations self._session.register( 'creating-resource-class.ec2.ServiceResource', - boto3.utils.lazy_call( - 'boto3.ec2.createtags.inject_create_tags')) + boto3.utils.lazy_call('boto3.ec2.createtags.inject_create_tags'), + ) self._session.register( 'creating-resource-class.ec2.Instance', boto3.utils.lazy_call( 'boto3.ec2.deletetags.inject_delete_tags', - event_emitter=self.events)) + event_emitter=self.events, + ), + ) diff --git a/boto3/utils.py b/boto3/utils.py index 2d11a7e..27561ad 100644 --- a/boto3/utils.py +++ b/boto3/utils.py @@ -13,11 +13,14 @@ import sys from collections import namedtuple - _ServiceContext = namedtuple( 'ServiceContext', - ['service_name', 'service_model', 'service_waiter_model', - 'resource_json_definitions'] + [ + 'service_name', + 'service_model', + 'service_waiter_model', + 'resource_json_definitions', + ], ) @@ -40,6 +43,7 @@ class ServiceContext(_ServiceContext): shapes for a service. It is equivalient of loading a ``resource-1.json`` and retrieving the value at the key "resources". """ + pass @@ -68,13 +72,14 @@ def lazy_call(full_name, **kwargs): def inject_attribute(class_attributes, name, value): if name in class_attributes: raise RuntimeError( - 'Cannot inject class attribute "%s", attribute ' - 'already exists in class dict.' % name) + f'Cannot inject class attribute "{name}", attribute ' + f'already exists in class dict.' + ) else: class_attributes[name] = value -class LazyLoadedWaiterModel(object): +class LazyLoadedWaiterModel: """A lazily loaded waiter model This does not load the service waiter model until an attempt is made @@ -83,6 +88,7 @@ class LazyLoadedWaiterModel(object): the waiter-2.json until it is accessed through a ``get_waiter`` call when the docstring is generated/accessed. """ + def __init__(self, bc_session, service_name, api_version): self._session = bc_session self._service_name = service_name @@ -90,4 +96,5 @@ class LazyLoadedWaiterModel(object): def get_waiter(self, waiter_name): return self._session.get_waiter_model( - self._service_name, self._api_version).get_waiter(waiter_name) + self._service_name, self._api_version + ).get_waiter(waiter_name) diff --git a/docs/source/guide/configuration.rst b/docs/source/guide/configuration.rst index 86ea64c..3536ca0 100644 --- a/docs/source/guide/configuration.rst +++ b/docs/source/guide/configuration.rst @@ -83,7 +83,7 @@ In the following example, a proxy list is set up to use ``proxy.amazon.com``, po client = boto3.client('kinesis', config=my_config) -Alternatively, you can use the ``HTTP_PROXY`` and ``HTTPS_PROXY`` environment variables to specify proxy servers. The ``NO_PROXY`` environment variable can be used to override proxy servers set by ``HTTP_PROXY`` and ``HTTPS_PROXY``. Proxy servers specified using the ``proxies`` option in the ``Config`` object will override proxy servers specified using environment variables. +Alternatively, you can use the ``HTTP_PROXY`` and ``HTTPS_PROXY`` environment variables to specify proxy servers. Proxy servers specified using the ``proxies`` option in the ``Config`` object will override proxy servers specified using environment variables. .. _configure_proxies: diff --git a/docs/source/guide/ec2-example-security-group.rst b/docs/source/guide/ec2-example-security-group.rst index bdc4d4d..fb449ae 100644 --- a/docs/source/guide/ec2-example-security-group.rst +++ b/docs/source/guide/ec2-example-security-group.rst @@ -56,7 +56,7 @@ Prerequisite tasks To set up and run this example, you must first configure your AWS credentials, as described in :doc:`quickstart`. Describe security groups -======================= +======================== Describe one or more of your security groups. A security group is for use with instances either in the EC2-Classic platform or in a specific VPC. @@ -65,6 +65,11 @@ in the *Amazon Elastic Compute Cloud User Guide* and `Security Groups for Your VPC `_ in the *Amazon Virtual Private Cloud User Guide*. +.. warning:: + We are retiring EC2-Classic on August 15, 2022. We recommend that you + migrate from EC2-Classic to a VPC. For more information, see *Migrate from + EC2-Classic to a VPC* in the `Amazon EC2 User Guide for Linux Instances `_ or the `Amazon EC2 User Guide for Windows Users `_. Also see the blog post `EC2-Classic Networking is Retiring – Here's How to Prepare `_. + The example below shows how to: * Describe a Security Group using @@ -147,6 +152,11 @@ If you attempt to delete a security group that is associated with an instance, o another security group, the operation fails with :code:`InvalidGroup.InUse` in EC2-Classic or :code:`DependencyViolation` in EC2-VPC. +.. warning:: + We are retiring EC2-Classic on August 15, 2022. We recommend that you + migrate from EC2-Classic to a VPC. For more information, see *Migrate from + EC2-Classic to a VPC* in the `Amazon EC2 User Guide for Linux Instances `_ or the `Amazon EC2 User Guide for Windows Users `_. Also see the blog post `EC2-Classic Networking is Retiring – Here's How to Prepare `_. + The example below shows how to: * Delete a security group using diff --git a/docs/source/guide/quickstart.rst b/docs/source/guide/quickstart.rst index e99ef37..46a4002 100644 --- a/docs/source/guide/quickstart.rst +++ b/docs/source/guide/quickstart.rst @@ -68,13 +68,13 @@ and throughput optimizations across AWS SDKs. When the AWS CRT is included, Boto3 uses it to incorporate features not otherwise available in the AWS SDK for Python. -At this time, Boto3 uses the AWS CRT's authentication package (`aws-c-auth -`_) to add support for the `AWS Signature Version 4 -`_ (sigv4) signer, which -adds authentication to your AWS requests using your security credentials (your AWS access key and -secret access key). +You'll find it used in features like: -Boto3 doesn't use the AWS CRT by default but you can opt into using it by specifying the +- `Amazon S3 Multi-Region Access Points `_ +- `Amazon S3 Object Integrity `_ +- Amazon EventBridge Global Endpoints + +However, Boto3 doesn't use the AWS CRT by default but you can opt into using it by specifying the :code:`crt` `extra feature `_ when installing Boto3:: pip install boto3[crt] diff --git a/docs/source/guide/s3-example-configuring-buckets.rst b/docs/source/guide/s3-example-configuring-buckets.rst index 7e6d379..c404a8b 100644 --- a/docs/source/guide/s3-example-configuring-buckets.rst +++ b/docs/source/guide/s3-example-configuring-buckets.rst @@ -68,7 +68,7 @@ method. 'AllowedHeaders': ['Authorization'], 'AllowedMethods': ['GET', 'PUT'], 'AllowedOrigins': ['*'], - 'ExposeHeaders': ['GET', 'PUT'], + 'ExposeHeaders': ['ETag', 'x-amz-request-id'], 'MaxAgeSeconds': 3000 }] } diff --git a/docs/source/guide/security.rst b/docs/source/guide/security.rst index 7f93c50..5f3a4c3 100644 --- a/docs/source/guide/security.rst +++ b/docs/source/guide/security.rst @@ -192,9 +192,9 @@ To ensure the SDK or CLI doesn't not negotiate for anything earlier than TLS 1.2 #!/usr/bin/env bash set -e - OPENSSL_VERSION="1.1.1d" + OPENSSL_VERSION="1.1.1m" OPENSSL_PREFIX="/opt/openssl-with-min-tls1_2" - PYTHON_VERSION="3.8.1" + PYTHON_VERSION="3.9.10" PYTHON_PREFIX="/opt/python-with-min-tls1_2" @@ -223,6 +223,44 @@ After you run this script, you should be able to use this newly installed versio This should print out:: - Python 3.8.1 + Python 3.9.10 To confirm this new version of Python does not negotiate a version earlier than TLS 1.2, rerun the steps from `Determining Supported Protocols`_ using the newly installed Python version (that is, ``/opt/python-with-min-tls1_2/bin/python3``). + +Enforcing TLS 1.3 +------------------ + +.. note:: + Some AWS Services do not yet support TLS 1.3, configuring this as your minimum version may affect SDK interoperability. + We recommend testing this change with each service prior to production deployment. + + +The process of ensuring the AWS SDK for Python uses no TLS version earlier than TLS 1.3 is the same as the instructions in the `Enforcing TLS 1.2`_ section with some minor modifications, primarily adding the ``no-tls1_2`` flag to the openssl build configuration. + +The following are the modified build instructions:: + + + #!/usr/bin/env bash + set -e + + OPENSSL_VERSION="1.1.1m" + OPENSSL_PREFIX="/opt/openssl-with-min-tls1_3" + PYTHON_VERSION="3.9.10" + PYTHON_PREFIX="/opt/python-with-min-tls1_3" + + + curl -O "https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz" + tar -xzf "openssl-$OPENSSL_VERSION.tar.gz" + cd openssl-$OPENSSL_VERSION + ./config --prefix=$OPENSSL_PREFIX no-ssl3 no-tls1 no-tls1_1 no-tls1_2 no-shared + make > /dev/null + sudo make install_sw > /dev/null + + + cd /tmp + curl -O "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz" + tar -xzf "Python-$PYTHON_VERSION.tgz" + cd Python-$PYTHON_VERSION + ./configure --prefix=$PYTHON_PREFIX --with-openssl=$OPENSSL_PREFIX --disable-shared > /dev/null + make > /dev/null + sudo make install > /dev/null diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..969cbc2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[tool.pytest.ini_options] +markers = [ + "slow: marks tests as slow", +] + +[tool.isort] +profile = "black" +line_length = 79 +honor_noqa = true +src_paths = ["boto3", "tests"] + +[tool.black] +line-length = 79 +skip_string_normalization = true diff --git a/requirements-dev-lock.txt b/requirements-dev-lock.txt index 30bfb46..2d558c9 100644 --- a/requirements-dev-lock.txt +++ b/requirements-dev-lock.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.7 # To update, run: # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements-dev-lock.txt requirements-dev.txt @@ -82,11 +82,6 @@ iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 # via pytest -nose==1.3.7 \ - --hash=sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac \ - --hash=sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a \ - --hash=sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98 - # via -r requirements-dev.txt packaging==21.0 \ --hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 \ --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 @@ -103,16 +98,16 @@ pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b # via packaging -pytest-cov==2.12.1 \ - --hash=sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a \ - --hash=sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7 - # via -r requirements-dev.txt pytest==6.2.5 \ --hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \ --hash=sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134 # via # -r requirements-dev.txt # pytest-cov +pytest-cov==2.12.1 \ + --hash=sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a \ + --hash=sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7 + # via -r requirements-dev.txt toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f diff --git a/requirements-dev.txt b/requirements-dev.txt index ba8798b..0a4e623 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,3 @@ -nose==1.3.7 wheel==0.37.0 coverage==5.5 diff --git a/requirements.txt b/requirements.txt index 615d16e..8184008 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -e git+https://github.com/boto/botocore.git@develop#egg=botocore --e git+https://github.com/boto/jmespath.git@develop#egg=jmespath +-e 'git+https://github.com/boto/jmespath.git@develop#egg=jmespath; python_version > "3.6"' +jmespath<1.0; python_version <= '3.6' -e git+https://github.com/boto/s3transfer.git@develop#egg=s3transfer diff --git a/scripts/ci/run-crt-tests b/scripts/ci/run-crt-tests index 9ff2f06..ceb6071 100755 --- a/scripts/ci/run-crt-tests +++ b/scripts/ci/run-crt-tests @@ -29,7 +29,7 @@ def run(command): try: - import awscrt # noqa + import awscrt # noqa except ImportError: print("MISSING DEPENDENCY: awscrt must be installed to run the crt tests.") sys.exit(1) diff --git a/scripts/ci/run-tests b/scripts/ci/run-tests index 94e94e5..bf74974 100755 --- a/scripts/ci/run-tests +++ b/scripts/ci/run-tests @@ -33,9 +33,7 @@ def process_args(args): runner = args.test_runner test_args = "" if args.with_cov: - test_args += ( - f"--cov={PACKAGE} --cov-report xml -v " - ) + test_args += f"--cov={PACKAGE} --cov-report xml -v " dirs = " ".join(args.test_dirs) return runner, test_args, dirs diff --git a/scripts/new-change b/scripts/new-change index 4d0cbcd..650298e 100755 --- a/scripts/new-change +++ b/scripts/new-change @@ -36,28 +36,26 @@ You can then use the ``scripts/gen-changelog`` to generate the CHANGELOG.rst file. """ -import os -import re -import sys -import json -import string -import random -import tempfile -import subprocess import argparse - +import json +import os +import random +import re +import string +import subprocess +import sys +import tempfile VALID_CHARS = set(string.ascii_letters + string.digits) CHANGES_DIR = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - '.changes' + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), '.changes' ) TEMPLATE = """\ # Type should be one of: feature, bugfix, enhancement, api-change # feature: A larger feature or change in behavior, usually resulting in a # minor version bump. # bugfix: Fixing a bug in an existing code path. -# enhancment: Small change to an underlying implementation detail. +# enhancement: Small change to an underlying implementation detail. # api-change: Changes to a modeled API. type: {change_type} @@ -90,7 +88,8 @@ def new_changelog_entry(args): parsed_values = get_values_from_editor(args) if has_empty_values(parsed_values): sys.stderr.write( - "Empty changelog values received, skipping entry creation.\n") + "Empty changelog values received, skipping entry creation.\n" + ) return 1 replace_issue_references(parsed_values, args.repo) write_new_change(parsed_values) @@ -98,9 +97,11 @@ def new_changelog_entry(args): def has_empty_values(parsed_values): - return not (parsed_values.get('type') and - parsed_values.get('category') and - parsed_values.get('description')) + return not ( + parsed_values.get('type') + and parsed_values.get('category') + and parsed_values.get('description') + ) def all_values_provided(args): @@ -118,7 +119,7 @@ def get_values_from_editor(args): f.flush() env = os.environ editor = env.get('VISUAL', env.get('EDITOR', 'vim')) - p = subprocess.Popen('%s %s' % (editor, f.name), shell=True) + p = subprocess.Popen(f'{editor} {f.name}', shell=True) p.communicate() with open(f.name) as f: filled_in_contents = f.read() @@ -131,9 +132,9 @@ def replace_issue_references(parsed, repo_name): def linkify(match): number = match.group()[1:] - return ( - '`%s `__' % ( - match.group(), repo_name, number)) + return '`{} `__'.format( + match.group(), repo_name, number + ) new_description = re.sub(r'#\d+', linkify, description) parsed['description'] = new_description @@ -151,13 +152,15 @@ def write_new_change(parsed_values): category = parsed_values['category'] short_summary = ''.join(filter(lambda x: x in VALID_CHARS, category)) filename = '{type_name}-{summary}'.format( - type_name=parsed_values['type'], - summary=short_summary) + type_name=parsed_values['type'], summary=short_summary + ) possible_filename = os.path.join( - dirname, '%s-%s.json' % (filename, str(random.randint(1, 100000)))) + dirname, f'{filename}-{str(random.randint(1, 100000))}.json' + ) while os.path.isfile(possible_filename): possible_filename = os.path.join( - dirname, '%s-%s.json' % (filename, str(random.randint(1, 100000)))) + dirname, f'{filename}-{str(random.randint(1, 100000))}.json' + ) with open(possible_filename, 'w') as f: f.write(json.dumps(parsed_values, indent=2) + "\n") @@ -198,15 +201,21 @@ def parse_filled_in_contents(contents): def main(): parser = argparse.ArgumentParser() - parser.add_argument('-t', '--type', dest='change_type', - default='', choices=('bugfix', 'feature', - 'enhancement', 'api-change')) - parser.add_argument('-c', '--category', dest='category', - default='') - parser.add_argument('-d', '--description', dest='description', - default='') - parser.add_argument('-r', '--repo', default='boto/boto3', - help='Optional repo name, e.g: boto/boto3') + parser.add_argument( + '-t', + '--type', + dest='change_type', + default='', + choices=('bugfix', 'feature', 'enhancement', 'api-change'), + ) + parser.add_argument('-c', '--category', dest='category', default='') + parser.add_argument('-d', '--description', dest='description', default='') + parser.add_argument( + '-r', + '--repo', + default='boto/boto3', + help='Optional repo name, e.g: boto/boto3', + ) args = parser.parse_args() sys.exit(new_changelog_entry(args)) diff --git a/setup.cfg b/setup.cfg index d30b8b9..d03159d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,8 +3,8 @@ universal = 0 [metadata] requires_dist = - botocore>=1.23.34,<1.24.0 - jmespath>=0.7.1,<1.0.0 + botocore>=1.26.8,<1.27.0 + jmespath>=0.7.1,<2.0.0 s3transfer>=0.5.0,<0.6.0 [options.extras_require] diff --git a/setup.py b/setup.py index bf3fd5f..b213828 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,9 @@ VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.]+)['"]''') requires = [ - 'botocore>=1.23.34,<1.24.0', - 'jmespath>=0.7.1,<1.0.0', - 's3transfer>=0.5.0,<0.6.0' + 'botocore>=1.26.8,<1.27.0', + 'jmespath>=0.7.1,<2.0.0', + 's3transfer>=0.5.0,<0.6.0', ] @@ -33,12 +33,7 @@ setup( url='https://github.com/boto/boto3', scripts=[], packages=find_packages(exclude=['tests*']), - package_data={ - 'boto3': [ - 'data/aws/resources/*.json', - 'examples/*.rst' - ] - }, + package_data={'boto3': ['data/aws/resources/*.json', 'examples/*.rst']}, include_package_data=True, install_requires=requires, license="Apache License 2.0", diff --git a/tests/__init__.py b/tests/__init__.py index 169862a..7a08485 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,7 +13,6 @@ import random import time - import unittest from unittest import mock @@ -25,8 +24,7 @@ def unique_id(name): integration tests in parallel that must create remote resources. """ - return '{0}-{1}-{2}'.format(name, int(time.time()), - random.randint(0, 10000)) + return f'{name}-{int(time.time())}-{random.randint(0, 10000)}' class BaseTestCase(unittest.TestCase): @@ -34,6 +32,7 @@ class BaseTestCase(unittest.TestCase): A base test case which mocks out the low-level session to prevent any actual calls to Botocore. """ + def setUp(self): self.bc_session_patch = mock.patch('botocore.session.Session') self.bc_session_cls = self.bc_session_patch.start() diff --git a/tests/functional/docs/__init__.py b/tests/functional/docs/__init__.py index bb7e7ef..b62ab16 100644 --- a/tests/functional/docs/__init__.py +++ b/tests/functional/docs/__init__.py @@ -18,15 +18,14 @@ class BaseDocsFunctionalTests(unittest.TestCase): for line in lines: assert line in contents beginning = contents.find(line) - contents = contents[(beginning + len(line)):] + contents = contents[(beginning + len(line)) :] def get_class_document_block(self, class_name, contents): start_class_document = '.. py:class:: %s' % class_name start_index = contents.find(start_class_document) assert start_index != -1, 'Class is not found in contents' contents = contents[start_index:] - end_index = contents.find( - ' .. py:class::', len(start_class_document)) + end_index = contents.find(' .. py:class::', len(start_class_document)) return contents[:end_index] def get_method_document_block(self, method_name, contents): @@ -35,7 +34,8 @@ class BaseDocsFunctionalTests(unittest.TestCase): assert start_index != -1, 'Method is not found in contents' contents = contents[start_index:] end_index = contents.find( - ' .. py:method::', len(start_method_document)) + ' .. py:method::', len(start_method_document) + ) return contents[:end_index] def get_request_syntax_document_block(self, contents): @@ -43,8 +43,7 @@ class BaseDocsFunctionalTests(unittest.TestCase): start_index = contents.find(start_marker) assert start_index != -1, 'There is no request syntax section' contents = contents[start_index:] - end_index = contents.find( - ':type', len(start_marker)) + end_index = contents.find(':type', len(start_marker)) return contents[:end_index] def get_response_syntax_document_block(self, contents): @@ -52,8 +51,7 @@ class BaseDocsFunctionalTests(unittest.TestCase): start_index = contents.find(start_marker) assert start_index != -1, 'There is no response syntax section' contents = contents[start_index:] - end_index = contents.find( - '**Response Structure**', len(start_marker)) + end_index = contents.find('**Response Structure**', len(start_marker)) return contents[:end_index] def get_request_parameter_document_block(self, param_name, contents): diff --git a/tests/functional/docs/test_dynamodb.py b/tests/functional/docs/test_dynamodb.py index 262f5b9..032a9ea 100644 --- a/tests/functional/docs/test_dynamodb.py +++ b/tests/functional/docs/test_dynamodb.py @@ -10,119 +10,163 @@ # 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. -from tests.functional.docs import BaseDocsFunctionalTests - -from boto3.session import Session from boto3.docs.service import ServiceDocumenter +from boto3.session import Session +from tests.functional.docs import BaseDocsFunctionalTests class TestDynamoDBCustomizations(BaseDocsFunctionalTests): def setUp(self): self.documenter = ServiceDocumenter( - 'dynamodb', session=Session(region_name='us-east-1')) + 'dynamodb', session=Session(region_name='us-east-1') + ) self.generated_contents = self.documenter.document_service() self.generated_contents = self.generated_contents.decode('utf-8') def test_batch_writer_is_documented(self): - self.assert_contains_lines_in_order([ - '.. py:class:: DynamoDB.Table(name)', - ' * :py:meth:`batch_writer()`', - ' .. py:method:: batch_writer(overwrite_by_pkeys=None)'], - self.generated_contents + self.assert_contains_lines_in_order( + [ + '.. py:class:: DynamoDB.Table(name)', + ' * :py:meth:`batch_writer()`', + ' .. py:method:: batch_writer(overwrite_by_pkeys=None)', + ], + self.generated_contents, ) def test_document_interface_is_documented(self): contents = self.get_class_document_block( - 'DynamoDB.Table', self.generated_contents) + 'DynamoDB.Table', self.generated_contents + ) # Take an arbitrary method that uses the customization. method_contents = self.get_method_document_block('put_item', contents) # Make sure the request syntax is as expected. request_syntax_contents = self.get_request_syntax_document_block( - method_contents) - self.assert_contains_lines_in_order([ - 'response = table.put_item(', - 'Item={', - ('\'string\': \'string\'|123|Binary(b\'bytes\')' - '|True|None|set([\'string\'])|set([123])|' - 'set([Binary(b\'bytes\')])|[]|{}'), - '},', - 'Expected={', - '\'string\': {', - ('\'Value\': \'string\'|123' - '|Binary(b\'bytes\')|True|None|set([\'string\'])' - '|set([123])|set([Binary(b\'bytes\')])|[]|{},'), - '\'AttributeValueList\': [', - ('\'string\'|123|Binary(b\'bytes\')' - '|True|None|set([\'string\'])|set([123])|' - 'set([Binary(b\'bytes\')])|[]|{},')], - request_syntax_contents) + method_contents + ) + self.assert_contains_lines_in_order( + [ + 'response = table.put_item(', + 'Item={', + ( + '\'string\': \'string\'|123|Binary(b\'bytes\')' + '|True|None|set([\'string\'])|set([123])|' + 'set([Binary(b\'bytes\')])|[]|{}' + ), + '},', + 'Expected={', + '\'string\': {', + ( + '\'Value\': \'string\'|123' + '|Binary(b\'bytes\')|True|None|set([\'string\'])' + '|set([123])|set([Binary(b\'bytes\')])|[]|{},' + ), + '\'AttributeValueList\': [', + ( + '\'string\'|123|Binary(b\'bytes\')' + '|True|None|set([\'string\'])|set([123])|' + 'set([Binary(b\'bytes\')])|[]|{},' + ), + ], + request_syntax_contents, + ) # Make sure the response syntax is as expected. response_syntax_contents = self.get_response_syntax_document_block( - method_contents) - self.assert_contains_lines_in_order([ - '{', - '\'Attributes\': {', - ('\'string\': \'string\'|123|' - 'Binary(b\'bytes\')|True|None|set([\'string\'])|' - 'set([123])|set([Binary(b\'bytes\')])|[]|{}'), - '},'], - response_syntax_contents) + method_contents + ) + self.assert_contains_lines_in_order( + [ + '{', + '\'Attributes\': {', + ( + '\'string\': \'string\'|123|' + 'Binary(b\'bytes\')|True|None|set([\'string\'])|' + 'set([123])|set([Binary(b\'bytes\')])|[]|{}' + ), + '},', + ], + response_syntax_contents, + ) # Make sure the request parameter is documented correctly. request_param_contents = self.get_request_parameter_document_block( - 'Item', method_contents) - self.assert_contains_lines_in_order([ - ':type Item: dict', - ':param Item: **[REQUIRED]**', - '- *(string) --*', - ('- *(valid DynamoDB type) --* - The value of the ' - 'attribute. The valid value types are listed in the ' - ':ref:`DynamoDB Reference Guide`.')], - request_param_contents + 'Item', method_contents + ) + self.assert_contains_lines_in_order( + [ + ':type Item: dict', + ':param Item: **[REQUIRED]**', + '- *(string) --*', + ( + '- *(valid DynamoDB type) --* - The value of the ' + 'attribute. The valid value types are listed in the ' + ':ref:`DynamoDB Reference Guide`.' + ), + ], + request_param_contents, ) # Make sure the response parameter is documented correctly. response_param_contents = self.get_response_parameter_document_block( - 'Attributes', method_contents) - self.assert_contains_lines_in_order([ - '- **Attributes** *(dict) --*', - '- *(string) --*', - ('- *(valid DynamoDB type) --* - The value of ' - 'the attribute. The valid value types are listed in the ' - ':ref:`DynamoDB Reference Guide`.')], - response_param_contents) + 'Attributes', method_contents + ) + self.assert_contains_lines_in_order( + [ + '- **Attributes** *(dict) --*', + '- *(string) --*', + ( + '- *(valid DynamoDB type) --* - The value of ' + 'the attribute. The valid value types are listed in the ' + ':ref:`DynamoDB Reference Guide`.' + ), + ], + response_param_contents, + ) def test_conditions_is_documented(self): contents = self.get_class_document_block( - 'DynamoDB.Table', self.generated_contents) + 'DynamoDB.Table', self.generated_contents + ) # Take an arbitrary method that uses the customization. method_contents = self.get_method_document_block('query', contents) # Make sure the request syntax is as expected. request_syntax_contents = self.get_request_syntax_document_block( - method_contents) - self.assert_contains_lines_in_order([ - 'response = table.query(', - ('FilterExpression=Attr(\'myattribute\').' - 'eq(\'myvalue\'),'), - ('KeyConditionExpression=Key(\'mykey\')' - '.eq(\'myvalue\'),')], - request_syntax_contents) + method_contents + ) + self.assert_contains_lines_in_order( + [ + 'response = table.query(', + ('FilterExpression=Attr(\'myattribute\').' 'eq(\'myvalue\'),'), + ('KeyConditionExpression=Key(\'mykey\')' '.eq(\'myvalue\'),'), + ], + request_syntax_contents, + ) # Make sure the request parameter is documented correctly. - self.assert_contains_lines_in_order([ - (':type FilterExpression: condition from ' - ':py:class:`boto3.dynamodb.conditions.Attr` method'), - (':param FilterExpression: The condition(s) an ' - 'attribute(s) must meet. Valid conditions are listed in ' - 'the :ref:`DynamoDB Reference Guide`.'), - (':type KeyConditionExpression: condition from ' - ':py:class:`boto3.dynamodb.conditions.Key` method'), - (':param KeyConditionExpression: The condition(s) a ' - 'key(s) must meet. Valid conditions are listed in the ' - ':ref:`DynamoDB Reference Guide`.')], - method_contents) + self.assert_contains_lines_in_order( + [ + ( + ':type FilterExpression: condition from ' + ':py:class:`boto3.dynamodb.conditions.Attr` method' + ), + ( + ':param FilterExpression: The condition(s) an ' + 'attribute(s) must meet. Valid conditions are listed in ' + 'the :ref:`DynamoDB Reference Guide`.' + ), + ( + ':type KeyConditionExpression: condition from ' + ':py:class:`boto3.dynamodb.conditions.Key` method' + ), + ( + ':param KeyConditionExpression: The condition(s) a ' + 'key(s) must meet. Valid conditions are listed in the ' + ':ref:`DynamoDB Reference Guide`.' + ), + ], + method_contents, + ) diff --git a/tests/functional/docs/test_ec2.py b/tests/functional/docs/test_ec2.py index 979b038..d1d7551 100644 --- a/tests/functional/docs/test_ec2.py +++ b/tests/functional/docs/test_ec2.py @@ -10,10 +10,9 @@ # 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. -from tests.functional.docs import BaseDocsFunctionalTests - -from boto3.session import Session from boto3.docs.service import ServiceDocumenter +from boto3.session import Session +from tests.functional.docs import BaseDocsFunctionalTests class TestInstanceDeleteTags(BaseDocsFunctionalTests): @@ -37,5 +36,5 @@ class TestInstanceDeleteTags(BaseDocsFunctionalTests): 'DryRun=True|False,', 'Tags=[', ], - method_contents + method_contents, ) diff --git a/tests/functional/docs/test_s3.py b/tests/functional/docs/test_s3.py index 0c15919..09f834f 100644 --- a/tests/functional/docs/test_s3.py +++ b/tests/functional/docs/test_s3.py @@ -10,25 +10,27 @@ # 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. -from tests.functional.docs import BaseDocsFunctionalTests - -from boto3.session import Session from boto3.docs.service import ServiceDocumenter +from boto3.session import Session +from tests.functional.docs import BaseDocsFunctionalTests class TestS3Customizations(BaseDocsFunctionalTests): def setUp(self): self.documenter = ServiceDocumenter( - 's3', session=Session(region_name='us-east-1')) + 's3', session=Session(region_name='us-east-1') + ) self.generated_contents = self.documenter.document_service() self.generated_contents = self.generated_contents.decode('utf-8') def test_file_transfer_methods_are_documented(self): - self.assert_contains_lines_in_order([ - '.. py:class:: S3.Client', - ' * :py:meth:`~S3.Client.download_file`', - ' * :py:meth:`~S3.Client.upload_file`', - ' .. py:method:: download_file(', - ' .. py:method:: upload_file('], - self.generated_contents + self.assert_contains_lines_in_order( + [ + '.. py:class:: S3.Client', + ' * :py:meth:`~S3.Client.download_file`', + ' * :py:meth:`~S3.Client.upload_file`', + ' .. py:method:: download_file(', + ' .. py:method:: upload_file(', + ], + self.generated_contents, ) diff --git a/tests/functional/docs/test_smoke.py b/tests/functional/docs/test_smoke.py index c4ea19e..30d9326 100644 --- a/tests/functional/docs/test_smoke.py +++ b/tests/functional/docs/test_smoke.py @@ -10,8 +10,8 @@ # 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. -import pytest import botocore.session +import pytest from botocore import xform_name from botocore.exceptions import DataNotFoundError @@ -31,8 +31,7 @@ def boto3_session(): def all_services(): session = boto3.Session(region_name='us-east-1') - for service_name in session.get_available_services(): - yield service_name + yield from session.get_available_services() @pytest.fixture @@ -46,7 +45,8 @@ def test_documentation( boto3_session, botocore_session, available_resources, service_name ): generated_docs = ServiceDocumenter( - service_name, session=boto3_session).document_service() + service_name, session=boto3_session + ).document_service() generated_docs = generated_docs.decode('utf-8') client = boto3.client(service_name, 'us-east-1') @@ -69,8 +69,10 @@ def test_documentation( try: paginator_model = botocore_session.get_paginator_model(service_name) _assert_has_paginator_documentation( - generated_docs, service_name, client, - sorted(paginator_model._paginator_config) + generated_docs, + service_name, + client, + sorted(paginator_model._paginator_config), ) except DataNotFoundError: pass @@ -87,16 +89,12 @@ def _assert_contains_lines_in_order(lines, contents): for line in lines: assert line in contents beginning = contents.find(line) - contents = contents[(beginning + len(line)):] + contents = contents[(beginning + len(line)) :] def _assert_has_title(generated_docs, client): title = client.__class__.__name__ - ref_lines = [ - '*' * len(title), - title, - '*' * len(title) - ] + ref_lines = ['*' * len(title), title, '*' * len(title)] _assert_contains_lines_in_order(ref_lines, generated_docs) @@ -119,50 +117,54 @@ def _assert_has_client_documentation(generated_docs, service_name, client): _assert_contains_lines_in_order(ref_lines, generated_docs) -def _assert_has_paginator_documentation(generated_docs, service_name, client, - paginator_names): +def _assert_has_paginator_documentation( + generated_docs, service_name, client, paginator_names +): ref_lines = [ '==========', 'Paginators', '==========', - 'The available paginators are:' + 'The available paginators are:', ] for paginator_name in paginator_names: ref_lines.append( - '* :py:class:`%s.Paginator.%s`' % ( - client.__class__.__name__, paginator_name)) + '* :py:class:`{}.Paginator.{}`'.format( + client.__class__.__name__, paginator_name + ) + ) for paginator_name in paginator_names: ref_lines.append( - '.. py:class:: %s.Paginator.%s' % ( - client.__class__.__name__, paginator_name)) - ref_lines.append( - ' .. py:method:: paginate(') + '.. py:class:: {}.Paginator.{}'.format( + client.__class__.__name__, paginator_name + ) + ) + ref_lines.append(' .. py:method:: paginate(') _assert_contains_lines_in_order(ref_lines, generated_docs) -def _assert_has_waiter_documentation(generated_docs, service_name, client, - waiter_model): - ref_lines = [ - '=======', - 'Waiters', - '=======', - 'The available waiters are:' - ] +def _assert_has_waiter_documentation( + generated_docs, service_name, client, waiter_model +): + ref_lines = ['=======', 'Waiters', '=======', 'The available waiters are:'] for waiter_name in waiter_model.waiter_names: ref_lines.append( - '* :py:class:`%s.Waiter.%s`' % ( - client.__class__.__name__, waiter_name)) + '* :py:class:`{}.Waiter.{}`'.format( + client.__class__.__name__, waiter_name + ) + ) for waiter_name in waiter_model.waiter_names: ref_lines.append( - '.. py:class:: %s.Waiter.%s' % ( - client.__class__.__name__, waiter_name)) + '.. py:class:: {}.Waiter.{}'.format( + client.__class__.__name__, waiter_name + ) + ) ref_lines.append( - ' waiter = client.get_waiter(\'%s\')' % xform_name(waiter_name)) - ref_lines.append( - ' .. py:method:: wait(') + ' waiter = client.get_waiter(\'%s\')' % xform_name(waiter_name) + ) + ref_lines.append(' .. py:method:: wait(') _assert_contains_lines_in_order(ref_lines, generated_docs) @@ -172,10 +174,10 @@ def _assert_has_resource_documentation(generated_docs, service_name, resource): '================', 'Service Resource', '================', - '.. py:class:: %s.ServiceResource' % ( - resource.meta.client.__class__.__name__), + '.. py:class:: %s.ServiceResource' + % (resource.meta.client.__class__.__name__), ' A resource representing', ' import boto3', - ' %s = boto3.resource(\'%s\')' % (service_name, service_name), + f' {service_name} = boto3.resource(\'{service_name}\')', ] _assert_contains_lines_in_order(ref_lines, generated_docs) diff --git a/tests/functional/dynamodb/test_stubber_conditions.py b/tests/functional/dynamodb/test_stubber_conditions.py index 3139c61..cde9388 100644 --- a/tests/functional/dynamodb/test_stubber_conditions.py +++ b/tests/functional/dynamodb/test_stubber_conditions.py @@ -37,22 +37,22 @@ class TestStubberSupportsFilterExpressions(unittest.TestCase): expected_params=dict( TableName='mytable', KeyConditionExpression=key_expr, - FilterExpression=filter_expr - ) + FilterExpression=filter_expr, + ), ) with stubber: - response = table.query(KeyConditionExpression=key_expr, - FilterExpression=filter_expr) + response = table.query( + KeyConditionExpression=key_expr, FilterExpression=filter_expr + ) assert response['Items'] == [] stubber.assert_no_pending_responses() def test_table_scan_can_be_stubbed_with_expressions(self): table = self.resource.Table('mytable') - filter_expr = ( - Attr('myattr').eq('foo') & - (Attr('myattr2').lte('buzz') | Attr('myattr2').gte('fizz')) + filter_expr = Attr('myattr').eq('foo') & ( + Attr('myattr2').lte('buzz') | Attr('myattr2').gte('fizz') ) stubber = Stubber(table.meta.client) @@ -60,9 +60,8 @@ class TestStubberSupportsFilterExpressions(unittest.TestCase): 'scan', dict(Items=list()), expected_params=dict( - TableName='mytable', - FilterExpression=filter_expr - ) + TableName='mytable', FilterExpression=filter_expr + ), ) with stubber: diff --git a/tests/functional/dynamodb/test_table.py b/tests/functional/dynamodb/test_table.py index b96bfe8..bf6b1c4 100644 --- a/tests/functional/dynamodb/test_table.py +++ b/tests/functional/dynamodb/test_table.py @@ -10,10 +10,10 @@ # 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. -from tests import unittest +from botocore.stub import Stubber import boto3 -from botocore.stub import Stubber +from tests import unittest class TestTableResourceCustomizations(unittest.TestCase): @@ -35,8 +35,7 @@ class TestTableResourceCustomizations(unittest.TestCase): with stubber: table.meta.client.tag_resource( - ResourceArn=arn, - Tags=[{'Key': 'project', 'Value': 'val'}] + ResourceArn=arn, Tags=[{'Key': 'project', 'Value': 'val'}] ) stubber.assert_no_pending_responses() diff --git a/tests/functional/test_collection.py b/tests/functional/test_collection.py index 3afe483..c00b8c1 100644 --- a/tests/functional/test_collection.py +++ b/tests/functional/test_collection.py @@ -10,23 +10,27 @@ # 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. -from tests import unittest - -from boto3.session import Session from boto3.resources.collection import ResourceCollection +from boto3.session import Session +from tests import unittest class TestCollection(unittest.TestCase): def setUp(self): self.session = Session( - aws_access_key_id='dummy', aws_secret_access_key='dummy', - region_name='us-east-1') + aws_access_key_id='dummy', + aws_secret_access_key='dummy', + region_name='us-east-1', + ) # Pick an arbitrary resource. self.ec2_resource = self.session.resource('ec2') def test_can_use_collection_methods(self): - assert isinstance(self.ec2_resource.instances.all(), ResourceCollection) + assert isinstance( + self.ec2_resource.instances.all(), ResourceCollection + ) def test_can_chain_methods(self): assert isinstance( - self.ec2_resource.instances.all().page_size(5), ResourceCollection) + self.ec2_resource.instances.all().page_size(5), ResourceCollection + ) diff --git a/tests/functional/test_dynamodb.py b/tests/functional/test_dynamodb.py index 81a0be2..bc378e4 100644 --- a/tests/functional/test_dynamodb.py +++ b/tests/functional/test_dynamodb.py @@ -11,12 +11,12 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import json -from tests import unittest, mock from botocore.awsrequest import AWSResponse -from boto3.session import Session from boto3.dynamodb.conditions import Attr +from boto3.session import Session +from tests import mock, unittest class TestDynamoDB(unittest.TestCase): @@ -24,14 +24,18 @@ class TestDynamoDB(unittest.TestCase): self.http_response = AWSResponse(None, 200, {}, None) self.parsed_response = {} self.make_request_patch = mock.patch( - 'botocore.endpoint.Endpoint.make_request') + 'botocore.endpoint.Endpoint.make_request' + ) self.make_request_mock = self.make_request_patch.start() self.make_request_mock.return_value = ( - self.http_response, self.parsed_response) + self.http_response, + self.parsed_response, + ) self.session = Session( aws_access_key_id='dummy', aws_secret_access_key='dummy', - region_name='us-east-1') + region_name='us-east-1', + ) def tearDown(self): self.make_request_patch.stop() @@ -47,7 +51,7 @@ class TestDynamoDB(unittest.TestCase): 'TableName': 'MyTable', 'FilterExpression': '#n0 = :v0', 'ExpressionAttributeNames': {'#n0': 'mykey'}, - 'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}} + 'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}}, } def test_client(self): @@ -57,7 +61,7 @@ class TestDynamoDB(unittest.TestCase): TableName='MyTable', FilterExpression='#n0 = :v0', ExpressionAttributeNames={'#n0': 'mykey'}, - ExpressionAttributeValues={':v0': {'S': 'myvalue'}} + ExpressionAttributeValues={':v0': {'S': 'myvalue'}}, ) request = self.make_request_mock.call_args_list[0][0][1] request_params = json.loads(request['body'].decode('utf-8')) @@ -65,5 +69,5 @@ class TestDynamoDB(unittest.TestCase): 'TableName': 'MyTable', 'FilterExpression': '#n0 = :v0', 'ExpressionAttributeNames': {'#n0': 'mykey'}, - 'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}} + 'ExpressionAttributeValues': {':v0': {'S': 'myvalue'}}, } diff --git a/tests/functional/test_ec2.py b/tests/functional/test_ec2.py index 84c8072..3f3f86f 100644 --- a/tests/functional/test_ec2.py +++ b/tests/functional/test_ec2.py @@ -12,9 +12,10 @@ # language governing permissions and limitations under the License. import unittest -import boto3.session from botocore.stub import Stubber +import boto3.session + class TestInstanceDeleteTags(unittest.TestCase): def setUp(self): @@ -50,15 +51,12 @@ class TestInstanceDeleteTags(unittest.TestCase): stubber.add_response( method='describe_instances', - service_response={ - 'Reservations': [] - }, + service_response={'Reservations': []}, expected_params={ - 'Filters': [{ - 'Name': 'instance-state-name', - 'Values': ['running'] - }] - } + 'Filters': [ + {'Name': 'instance-state-name', 'Values': ['running']} + ] + }, ) with stubber: diff --git a/tests/functional/test_resource.py b/tests/functional/test_resource.py index 1cee592..f28788c 100644 --- a/tests/functional/test_resource.py +++ b/tests/functional/test_resource.py @@ -10,12 +10,11 @@ # 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. +import botocore.session import pytest import boto3 from boto3.exceptions import ResourceNotExistsError - -import botocore.session from tests import unittest @@ -30,12 +29,14 @@ class TestResourceCustomization(unittest.TestCase): def add_new_method(self, name): def handler(class_attributes, **kwargs): class_attributes[name] = identity + return handler def test_can_inject_method_onto_resource(self): session = boto3.Session(botocore_session=self.botocore_session) - self.botocore_session.register('creating-resource-class.s3', - self.add_new_method(name='my_method')) + self.botocore_session.register( + 'creating-resource-class.s3', self.add_new_method(name='my_method') + ) resource = session.resource('s3') assert hasattr(resource, 'my_method') assert resource.my_method('anything') == 'anything' diff --git a/tests/functional/test_s3.py b/tests/functional/test_s3.py index a7d8d79..43c978a 100644 --- a/tests/functional/test_s3.py +++ b/tests/functional/test_s3.py @@ -10,18 +10,16 @@ # 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. -import pytest - -from tests import unittest - import botocore import botocore.stub +import pytest +from botocore.compat import six from botocore.config import Config from botocore.stub import Stubber -from botocore.compat import six import boto3.session from boto3.s3.transfer import TransferConfig +from tests import unittest class TestS3MethodInjection(unittest.TestCase): @@ -53,8 +51,10 @@ class TestS3MethodInjection(unittest.TestCase): class BaseTransferTest(unittest.TestCase): def setUp(self): self.session = boto3.session.Session( - aws_access_key_id='foo', aws_secret_access_key='bar', - region_name='us-west-2') + aws_access_key_id='foo', + aws_secret_access_key='bar', + region_name='us-west-2', + ) self.s3 = self.session.resource('s3') self.stubber = Stubber(self.s3.meta.client) self.bucket = 'mybucket' @@ -73,25 +73,24 @@ class BaseTransferTest(unittest.TestCase): 'Metadata': {}, 'ResponseMetadata': { 'HTTPStatusCode': 200, - } + }, } if expected_params is None: - expected_params = { - 'Bucket': self.bucket, - 'Key': self.key - } + expected_params = {'Bucket': self.bucket, 'Key': self.key} self.stubber.add_response( - method='head_object', service_response=head_response, - expected_params=expected_params) + method='head_object', + service_response=head_response, + expected_params=expected_params, + ) def stub_create_multipart_upload(self): # Add the response and assert params for CreateMultipartUpload create_upload_response = { "Bucket": self.bucket, "Key": self.key, - "UploadId": self.upload_id + "UploadId": self.upload_id, } expected_params = { "Bucket": self.bucket, @@ -100,33 +99,33 @@ class BaseTransferTest(unittest.TestCase): self.stubber.add_response( method='create_multipart_upload', service_response=create_upload_response, - expected_params=expected_params) + expected_params=expected_params, + ) def stub_complete_multipart_upload(self, parts): complete_upload_response = { "Location": "us-west-2", "Bucket": self.bucket, "Key": self.key, - "ETag": self.etag + "ETag": self.etag, } expected_params = { "Bucket": self.bucket, "Key": self.key, - "MultipartUpload": { - "Parts": parts - }, - "UploadId": self.upload_id + "MultipartUpload": {"Parts": parts}, + "UploadId": self.upload_id, } self.stubber.add_response( method='complete_multipart_upload', service_response=complete_upload_response, - expected_params=expected_params) + expected_params=expected_params, + ) class TestCopy(BaseTransferTest): def setUp(self): - super(TestCopy, self).setUp() + super().setUp() self.copy_source = {'Bucket': 'foo', 'Key': 'bar'} def stub_single_part_copy(self): @@ -137,7 +136,8 @@ class TestCopy(BaseTransferTest): # Set the HEAD to return the total size total_size = part_size * num_parts self.stub_head( - content_length=total_size, expected_params=self.copy_source) + content_length=total_size, expected_params=self.copy_source + ) self.stub_create_multipart_upload() @@ -146,9 +146,8 @@ class TestCopy(BaseTransferTest): for i in range(num_parts): # Fill in the parts part_number = i + 1 - copy_range = "bytes=%s-%s" % ( - i * part_size, - i * part_size + (part_size - 1) + copy_range = "bytes={}-{}".format( + i * part_size, i * part_size + (part_size - 1) ) self.stub_copy_part(part_number=part_number, copy_range=copy_range) parts.append({'ETag': self.etag, 'PartNumber': part_number}) @@ -157,30 +156,24 @@ class TestCopy(BaseTransferTest): def stub_copy_object(self): copy_response = { - 'CopyObjectResult': { - 'ETag': self.etag - }, - 'ResponseMetadata': { - 'HTTPStatusCode': 200 - } + 'CopyObjectResult': {'ETag': self.etag}, + 'ResponseMetadata': {'HTTPStatusCode': 200}, } expected_params = { "Bucket": self.bucket, "Key": self.key, - "CopySource": self.copy_source + "CopySource": self.copy_source, } self.stubber.add_response( - method='copy_object', service_response=copy_response, - expected_params=expected_params) + method='copy_object', + service_response=copy_response, + expected_params=expected_params, + ) def stub_copy_part(self, part_number, copy_range): copy_part_response = { - "CopyPartResult": { - "ETag": self.etag - }, - 'ResponseMetadata': { - 'HTTPStatusCode': 200 - } + "CopyPartResult": {"ETag": self.etag}, + 'ResponseMetadata': {'HTTPStatusCode': 200}, } expected_params = { "Bucket": self.bucket, @@ -188,17 +181,20 @@ class TestCopy(BaseTransferTest): "CopySource": self.copy_source, "UploadId": self.upload_id, "PartNumber": part_number, - "CopySourceRange": copy_range + "CopySourceRange": copy_range, } self.stubber.add_response( - method='upload_part_copy', service_response=copy_part_response, - expected_params=expected_params) + method='upload_part_copy', + service_response=copy_part_response, + expected_params=expected_params, + ) def test_client_copy(self): self.stub_single_part_copy() with self.stubber: response = self.s3.meta.client.copy( - self.copy_source, self.bucket, self.key) + self.copy_source, self.bucket, self.key + ) # The response will be none on a successful transfer. assert response is None @@ -218,11 +214,13 @@ class TestCopy(BaseTransferTest): assert response is None def test_copy_progress(self): - chunksize = 8 * (1024 ** 2) + chunksize = 8 * (1024**2) self.stub_multipart_copy(chunksize, 3) transfer_config = TransferConfig( - multipart_chunksize=chunksize, multipart_threshold=1, - max_concurrency=1) + multipart_chunksize=chunksize, + multipart_threshold=1, + max_concurrency=1, + ) def progress_callback(amount): self.progress += amount @@ -230,8 +228,12 @@ class TestCopy(BaseTransferTest): with self.stubber: self.s3.meta.client.copy( - Bucket=self.bucket, Key=self.key, CopySource=self.copy_source, - Config=transfer_config, Callback=progress_callback) + Bucket=self.bucket, + Key=self.key, + CopySource=self.copy_source, + Config=transfer_config, + Callback=progress_callback, + ) # Assert that the progress callback was called the correct number of # times with the correct amounts. @@ -241,42 +243,42 @@ class TestCopy(BaseTransferTest): class TestUploadFileobj(BaseTransferTest): def setUp(self): - super(TestUploadFileobj, self).setUp() + super().setUp() self.contents = six.BytesIO(b'foo\n') def stub_put_object(self): put_object_response = { "ETag": self.etag, - "ResponseMetadata": { - "HTTPStatusCode": 200 - } + "ResponseMetadata": {"HTTPStatusCode": 200}, } expected_params = { "Bucket": self.bucket, "Key": self.key, - "Body": botocore.stub.ANY + "Body": botocore.stub.ANY, } self.stubber.add_response( - method='put_object', service_response=put_object_response, - expected_params=expected_params) + method='put_object', + service_response=put_object_response, + expected_params=expected_params, + ) def stub_upload_part(self, part_number): upload_part_response = { 'ETag': self.etag, - 'ResponseMetadata': { - 'HTTPStatusCode': 200 - } + 'ResponseMetadata': {'HTTPStatusCode': 200}, } expected_params = { "Bucket": self.bucket, "Key": self.key, "Body": botocore.stub.ANY, "PartNumber": part_number, - "UploadId": self.upload_id + "UploadId": self.upload_id, } self.stubber.add_response( - method='upload_part', service_response=upload_part_response, - expected_params=expected_params) + method='upload_part', + service_response=upload_part_response, + expected_params=expected_params, + ) def stub_multipart_upload(self, num_parts): self.stub_create_multipart_upload() @@ -296,7 +298,8 @@ class TestUploadFileobj(BaseTransferTest): with self.stubber: # The stubber will assert that all the right parameters are called. self.s3.meta.client.upload_fileobj( - Fileobj=self.contents, Bucket=self.bucket, Key=self.key) + Fileobj=self.contents, Bucket=self.bucket, Key=self.key + ) self.stubber.assert_no_pending_responses() @@ -304,7 +307,8 @@ class TestUploadFileobj(BaseTransferTest): with self.stubber: with pytest.raises(ValueError): self.s3.meta.client.upload_fileobj( - Fileobj='foo', Bucket=self.bucket, Key=self.key) + Fileobj='foo', Bucket=self.bucket, Key=self.key + ) def test_bucket_upload(self): self.stub_put_object() @@ -325,25 +329,30 @@ class TestUploadFileobj(BaseTransferTest): self.stubber.assert_no_pending_responses() def test_multipart_upload(self): - chunksize = 8 * (1024 ** 2) + chunksize = 8 * (1024**2) contents = six.BytesIO(b'0' * (chunksize * 3)) self.stub_multipart_upload(num_parts=3) transfer_config = TransferConfig( - multipart_chunksize=chunksize, multipart_threshold=1, - max_concurrency=1) + multipart_chunksize=chunksize, + multipart_threshold=1, + max_concurrency=1, + ) with self.stubber: # The stubber will assert that all the right parameters are called. self.s3.meta.client.upload_fileobj( - Fileobj=contents, Bucket=self.bucket, Key=self.key, - Config=transfer_config) + Fileobj=contents, + Bucket=self.bucket, + Key=self.key, + Config=transfer_config, + ) self.stubber.assert_no_pending_responses() class TestDownloadFileobj(BaseTransferTest): def setUp(self): - super(TestDownloadFileobj, self).setUp() + super().setUp() self.contents = b'foo' self.fileobj = six.BytesIO() @@ -377,31 +386,31 @@ class TestDownloadFileobj(BaseTransferTest): # If this is a ranged get, ContentRange needs to be returned, # contents needs to be pruned, and Range needs to be an expected param. if end_byte is not None: - contents = full_contents[start_byte:end_byte + 1] - part_range = 'bytes=%s-%s' % (start_byte, end_byte_range) - content_range = 'bytes=%s-%s/%s' % ( - start_byte, end_byte, len(full_contents)) + contents = full_contents[start_byte : end_byte + 1] + part_range = f'bytes={start_byte}-{end_byte_range}' + content_range = 'bytes={}-{}/{}'.format( + start_byte, end_byte, len(full_contents) + ) get_object_response['ContentRange'] = content_range expected_params['Range'] = part_range - get_object_response.update({ - "AcceptRanges": "bytes", - "ETag": self.etag, - "ContentLength": len(contents), - "ContentType": "binary/octet-stream", - "Body": six.BytesIO(contents), - "ResponseMetadata": { - "HTTPStatusCode": 200 + get_object_response.update( + { + "AcceptRanges": "bytes", + "ETag": self.etag, + "ContentLength": len(contents), + "ContentType": "binary/octet-stream", + "Body": six.BytesIO(contents), + "ResponseMetadata": {"HTTPStatusCode": 200}, } - }) - expected_params.update({ - "Bucket": self.bucket, - "Key": self.key - }) + ) + expected_params.update({"Bucket": self.bucket, "Key": self.key}) self.stubber.add_response( - method='get_object', service_response=get_object_response, - expected_params=expected_params) + method='get_object', + service_response=get_object_response, + expected_params=expected_params, + ) def stub_multipart_download(self, contents, part_size, num_parts): self.stub_head(content_length=len(contents)) @@ -410,14 +419,17 @@ class TestDownloadFileobj(BaseTransferTest): start_byte = i * part_size end_byte = (i + 1) * part_size - 1 self.stub_get_object( - full_contents=contents, start_byte=start_byte, - end_byte=end_byte) + full_contents=contents, + start_byte=start_byte, + end_byte=end_byte, + ) def test_client_download(self): self.stub_single_part_download() with self.stubber: self.s3.meta.client.download_fileobj( - Bucket=self.bucket, Key=self.key, Fileobj=self.fileobj) + Bucket=self.bucket, Key=self.key, Fileobj=self.fileobj + ) assert self.fileobj.getvalue() == self.contents self.stubber.assert_no_pending_responses() @@ -426,7 +438,8 @@ class TestDownloadFileobj(BaseTransferTest): with self.stubber: with pytest.raises(ValueError): self.s3.meta.client.download_fileobj( - Bucket=self.bucket, Key=self.key, Fileobj='foo') + Bucket=self.bucket, Key=self.key, Fileobj='foo' + ) def test_bucket_download(self): self.stub_single_part_download() @@ -449,15 +462,19 @@ class TestDownloadFileobj(BaseTransferTest): def test_multipart_download(self): self.contents = b'A' * 55 self.stub_multipart_download( - contents=self.contents, part_size=5, num_parts=11) + contents=self.contents, part_size=5, num_parts=11 + ) transfer_config = TransferConfig( - multipart_chunksize=5, multipart_threshold=1, - max_concurrency=1) + multipart_chunksize=5, multipart_threshold=1, max_concurrency=1 + ) with self.stubber: self.s3.meta.client.download_fileobj( - Bucket=self.bucket, Key=self.key, Fileobj=self.fileobj, - Config=transfer_config) + Bucket=self.bucket, + Key=self.key, + Fileobj=self.fileobj, + Config=transfer_config, + ) assert self.fileobj.getvalue() == self.contents self.stubber.assert_no_pending_responses() @@ -465,10 +482,11 @@ class TestDownloadFileobj(BaseTransferTest): def test_download_progress(self): self.contents = b'A' * 55 self.stub_multipart_download( - contents=self.contents, part_size=5, num_parts=11) + contents=self.contents, part_size=5, num_parts=11 + ) transfer_config = TransferConfig( - multipart_chunksize=5, multipart_threshold=1, - max_concurrency=1) + multipart_chunksize=5, multipart_threshold=1, max_concurrency=1 + ) def progress_callback(amount): self.progress += amount @@ -476,8 +494,12 @@ class TestDownloadFileobj(BaseTransferTest): with self.stubber: self.s3.meta.client.download_fileobj( - Bucket=self.bucket, Key=self.key, Fileobj=self.fileobj, - Config=transfer_config, Callback=progress_callback) + Bucket=self.bucket, + Key=self.key, + Fileobj=self.fileobj, + Config=transfer_config, + Callback=progress_callback, + ) # Assert that the progress callback was called the correct number of # times with the correct amounts. @@ -489,8 +511,10 @@ class TestDownloadFileobj(BaseTransferTest): class TestS3ObjectSummary(unittest.TestCase): def setUp(self): self.session = boto3.session.Session( - aws_access_key_id='foo', aws_secret_access_key='bar', - region_name='us-west-2') + aws_access_key_id='foo', + aws_secret_access_key='bar', + region_name='us-west-2', + ) self.s3 = self.session.resource('s3') self.obj_summary = self.s3.ObjectSummary('my_bucket', 'my_key') self.obj_summary_size = 12 @@ -499,13 +523,11 @@ class TestS3ObjectSummary(unittest.TestCase): self.stubber.add_response( method='head_object', service_response={ - 'ContentLength': self.obj_summary_size, 'ETag': 'my-etag', - 'ContentType': 'binary' + 'ContentLength': self.obj_summary_size, + 'ETag': 'my-etag', + 'ContentType': 'binary', }, - expected_params={ - 'Bucket': 'my_bucket', - 'Key': 'my_key' - } + expected_params={'Bucket': 'my_bucket', 'Key': 'my_key'}, ) def tearDown(self): diff --git a/tests/functional/test_session.py b/tests/functional/test_session.py index 1d00894..65c28b1 100644 --- a/tests/functional/test_session.py +++ b/tests/functional/test_session.py @@ -10,9 +10,8 @@ # 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. -from tests import unittest - import boto3.session +from tests import unittest class TestSession(unittest.TestCase): diff --git a/tests/functional/test_smoke.py b/tests/functional/test_smoke.py index 12a05ff..c56685b 100644 --- a/tests/functional/test_smoke.py +++ b/tests/functional/test_smoke.py @@ -10,10 +10,10 @@ # 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. +import botocore.session import pytest from boto3.session import Session -import botocore.session boto3_session = None @@ -24,7 +24,7 @@ def create_session(): boto3_session = Session( aws_access_key_id='dummy', aws_secret_access_key='dummy', - region_name='us-east-1' + region_name='us-east-1', ) return boto3_session @@ -77,7 +77,7 @@ def test_api_versions_synced_with_botocore(api_version_args): service_name, region_name='us-east-1', aws_access_key_id='foo', - aws_secret_access_key='bar' + aws_secret_access_key='bar', ) botocore_api_version = client.meta.service_model.api_version err = ( diff --git a/tests/functional/test_utils.py b/tests/functional/test_utils.py index faa8605..f7c6580 100644 --- a/tests/functional/test_utils.py +++ b/tests/functional/test_utils.py @@ -10,21 +10,20 @@ # 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. +import botocore.session import pytest -from tests import unittest - -import botocore.session - -from boto3 import utils import boto3.session +from boto3 import utils +from tests import unittest class TestUtils(unittest.TestCase): def test_runtime_error_raised_when_shadowing_client_method(self): botocore_session = botocore.session.get_session() - session = boto3.session.Session(region_name='us-west-2', - botocore_session=botocore_session) + session = boto3.session.Session( + region_name='us-west-2', botocore_session=botocore_session + ) def shadows_put_object(class_attributes, **kwargs): utils.inject_attribute(class_attributes, 'put_object', 'invalid') diff --git a/tests/integration/test_collections.py b/tests/integration/test_collections.py index 6c7f3a6..ceb4ff1 100644 --- a/tests/integration/test_collections.py +++ b/tests/integration/test_collections.py @@ -14,22 +14,19 @@ import pytest import boto3.session -from boto3.resources.collection import CollectionManager from boto3.resources.base import ServiceResource - +from boto3.resources.collection import CollectionManager # A map of services to regions that cannot use us-west-2 # for the integration tests. -REGION_MAP = { - 'opsworks': 'us-east-1' -} +REGION_MAP = {'opsworks': 'us-east-1'} # A list of collections to ignore. They require parameters # or are very slow to run. BLOCKLIST = { 'ec2': ['images'], 'iam': ['signing_certificates'], - 'sqs': ['dead_letter_source_queues'] + 'sqs': ['dead_letter_source_queues'], } @@ -39,8 +36,8 @@ def all_collections(): session = boto3.session.Session() for service_name in session.get_available_resources(): resource = session.resource( - service_name, - region_name=REGION_MAP.get(service_name, 'us-west-2')) + service_name, region_name=REGION_MAP.get(service_name, 'us-west-2') + ) for key in dir(resource): if key in BLOCKLIST.get(service_name, []): diff --git a/tests/integration/test_dynamodb.py b/tests/integration/test_dynamodb.py index 2453322..47ff8cd 100644 --- a/tests/integration/test_dynamodb.py +++ b/tests/integration/test_dynamodb.py @@ -14,9 +14,9 @@ from decimal import Decimal import boto3.session from boto3.compat import collections_abc -from boto3.dynamodb.types import Binary from boto3.dynamodb.conditions import Attr, Key -from tests import unittest, unique_id +from boto3.dynamodb.types import Binary +from tests import unique_id, unittest class BaseDynamoDBTest(unittest.TestCase): @@ -32,19 +32,23 @@ class BaseDynamoDBTest(unittest.TestCase): 'MyString': 'mystring', 'MyNumber': Decimal('1.25'), 'MyBinary': Binary(b'\x01'), - 'MyStringSet': set(['foo']), - 'MyNumberSet': set([Decimal('1.25')]), - 'MyBinarySet': set([Binary(b'\x01')]), + 'MyStringSet': {'foo'}, + 'MyNumberSet': {Decimal('1.25')}, + 'MyBinarySet': {Binary(b'\x01')}, 'MyList': ['foo'], - 'MyMap': {'foo': 'bar'} + 'MyMap': {'foo': 'bar'}, } cls.table = cls.dynamodb.create_table( TableName=cls.table_name, - ProvisionedThroughput={"ReadCapacityUnits": 5, - "WriteCapacityUnits": 5}, + ProvisionedThroughput={ + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5, + }, KeySchema=[{"AttributeName": "MyHashKey", "KeyType": "HASH"}], - AttributeDefinitions=[{"AttributeName": "MyHashKey", - "AttributeType": "S"}]) + AttributeDefinitions=[ + {"AttributeName": "MyHashKey", "AttributeType": "S"} + ], + ) waiter = cls.dynamodb.meta.client.get_waiter('table_exists') waiter.wait(TableName=cls.table_name) @@ -57,116 +61,111 @@ class TestDynamoDBTypes(BaseDynamoDBTest): def test_put_get_item(self): self.table.put_item(Item=self.item_data) self.addCleanup(self.table.delete_item, Key={'MyHashKey': 'mykey'}) - response = self.table.get_item(Key={'MyHashKey': 'mykey'}, - ConsistentRead=True) + response = self.table.get_item( + Key={'MyHashKey': 'mykey'}, ConsistentRead=True + ) self.assertEqual(response['Item'], self.item_data) class TestDynamoDBConditions(BaseDynamoDBTest): @classmethod def setUpClass(cls): - super(TestDynamoDBConditions, cls).setUpClass() + super().setUpClass() cls.table.put_item(Item=cls.item_data) @classmethod def tearDownClass(cls): cls.table.delete_item(Key={'MyHashKey': 'mykey'}) - super(TestDynamoDBConditions, cls).tearDownClass() + super().tearDownClass() def scan(self, filter_expression): - return self.table.scan(FilterExpression=filter_expression, - ConsistentRead=True) + return self.table.scan( + FilterExpression=filter_expression, ConsistentRead=True + ) def query(self, key_condition_expression, filter_expression=None): kwargs = { 'KeyConditionExpression': key_condition_expression, - 'ConsistentRead': True + 'ConsistentRead': True, } if filter_expression is not None: kwargs['FilterExpression'] = filter_expression return self.table.query(**kwargs) def test_filter_expression(self): - r = self.scan( - filter_expression=Attr('MyHashKey').eq('mykey')) + r = self.scan(filter_expression=Attr('MyHashKey').eq('mykey')) self.assertEqual(r['Items'][0]['MyHashKey'], 'mykey') def test_key_condition_expression(self): - r = self.query( - key_condition_expression=Key('MyHashKey').eq('mykey')) + r = self.query(key_condition_expression=Key('MyHashKey').eq('mykey')) self.assertEqual(r['Items'][0]['MyHashKey'], 'mykey') def test_key_condition_with_filter_condition_expression(self): r = self.query( key_condition_expression=Key('MyHashKey').eq('mykey'), - filter_expression=Attr('MyString').eq('mystring')) + filter_expression=Attr('MyString').eq('mystring'), + ) self.assertEqual(r['Items'][0]['MyString'], 'mystring') def test_condition_less_than(self): - r = self.scan( - filter_expression=Attr('MyNumber').lt(Decimal('1.26'))) + r = self.scan(filter_expression=Attr('MyNumber').lt(Decimal('1.26'))) self.assertTrue(r['Items'][0]['MyNumber'] < Decimal('1.26')) def test_condition_less_than_equal(self): - r = self.scan( - filter_expression=Attr('MyNumber').lte(Decimal('1.26'))) + r = self.scan(filter_expression=Attr('MyNumber').lte(Decimal('1.26'))) self.assertTrue(r['Items'][0]['MyNumber'] <= Decimal('1.26')) def test_condition_greater_than(self): - r = self.scan( - filter_expression=Attr('MyNumber').gt(Decimal('1.24'))) + r = self.scan(filter_expression=Attr('MyNumber').gt(Decimal('1.24'))) self.assertTrue(r['Items'][0]['MyNumber'] > Decimal('1.24')) def test_condition_greater_than_equal(self): - r = self.scan( - filter_expression=Attr('MyNumber').gte(Decimal('1.24'))) + r = self.scan(filter_expression=Attr('MyNumber').gte(Decimal('1.24'))) self.assertTrue(r['Items'][0]['MyNumber'] >= Decimal('1.24')) def test_condition_begins_with(self): - r = self.scan( - filter_expression=Attr('MyString').begins_with('my')) + r = self.scan(filter_expression=Attr('MyString').begins_with('my')) self.assertTrue(r['Items'][0]['MyString'].startswith('my')) def test_condition_between(self): r = self.scan( filter_expression=Attr('MyNumber').between( - Decimal('1.24'), Decimal('1.26'))) + Decimal('1.24'), Decimal('1.26') + ) + ) self.assertTrue(r['Items'][0]['MyNumber'] > Decimal('1.24')) self.assertTrue(r['Items'][0]['MyNumber'] < Decimal('1.26')) def test_condition_not_equal(self): - r = self.scan( - filter_expression=Attr('MyHashKey').ne('notmykey')) + r = self.scan(filter_expression=Attr('MyHashKey').ne('notmykey')) self.assertNotEqual(r['Items'][0]['MyHashKey'], 'notmykey') def test_condition_in(self): r = self.scan( - filter_expression=Attr('MyHashKey').is_in(['notmykey', 'mykey'])) + filter_expression=Attr('MyHashKey').is_in(['notmykey', 'mykey']) + ) self.assertIn(r['Items'][0]['MyHashKey'], ['notmykey', 'mykey']) def test_condition_exists(self): - r = self.scan( - filter_expression=Attr('MyString').exists()) + r = self.scan(filter_expression=Attr('MyString').exists()) self.assertIn('MyString', r['Items'][0]) def test_condition_not_exists(self): - r = self.scan( - filter_expression=Attr('MyFakeKey').not_exists()) + r = self.scan(filter_expression=Attr('MyFakeKey').not_exists()) self.assertNotIn('MyFakeKey', r['Items'][0]) def test_condition_contains(self): - r = self.scan( - filter_expression=Attr('MyString').contains('my')) + r = self.scan(filter_expression=Attr('MyString').contains('my')) self.assertIn('my', r['Items'][0]['MyString']) def test_condition_size(self): r = self.scan( - filter_expression=Attr('MyString').size().eq(len('mystring'))) + filter_expression=Attr('MyString').size().eq(len('mystring')) + ) self.assertEqual(len(r['Items'][0]['MyString']), len('mystring')) def test_condition_attribute_type(self): - r = self.scan( - filter_expression=Attr('MyMap').attribute_type('M')) + r = self.scan(filter_expression=Attr('MyMap').attribute_type('M')) self.assertIsInstance(r['Items'][0]['MyMap'], collections_abc.Mapping) def test_condition_and(self): @@ -177,32 +176,32 @@ class TestDynamoDBConditions(BaseDynamoDBTest): ) item = r['Items'][0] self.assertTrue( - item['MyHashKey'] == 'mykey' and item['MyString'] == 'mystring') + item['MyHashKey'] == 'mykey' and item['MyString'] == 'mystring' + ) def test_condition_or(self): r = self.scan( filter_expression=( - Attr('MyHashKey').eq('mykey2') | Attr('MyString').eq('mystring') + Attr('MyHashKey').eq('mykey2') + | Attr('MyString').eq('mystring') ) ) item = r['Items'][0] self.assertTrue( - item['MyHashKey'] == 'mykey2' or item['MyString'] == 'mystring') + item['MyHashKey'] == 'mykey2' or item['MyString'] == 'mystring' + ) def test_condition_not(self): - r = self.scan( - filter_expression=(~Attr('MyHashKey').eq('mykey2'))) + r = self.scan(filter_expression=(~Attr('MyHashKey').eq('mykey2'))) item = r['Items'][0] self.assertTrue(item['MyHashKey'] != 'mykey2') def test_condition_in_map(self): - r = self.scan( - filter_expression=Attr('MyMap.foo').eq('bar')) + r = self.scan(filter_expression=Attr('MyMap.foo').eq('bar')) self.assertEqual(r['Items'][0]['MyMap']['foo'], 'bar') def test_condition_in_list(self): - r = self.scan( - filter_expression=Attr('MyList[0]').eq('foo')) + r = self.scan(filter_expression=Attr('MyList[0]').eq('foo')) self.assertEqual(r['Items'][0]['MyList'][0], 'foo') @@ -211,8 +210,7 @@ class TestDynamodbBatchWrite(BaseDynamoDBTest): num_elements = 1000 items = [] for i in range(num_elements): - items.append({'MyHashKey': 'foo%s' % i, - 'OtherKey': 'bar%s' % i}) + items.append({'MyHashKey': 'foo%s' % i, 'OtherKey': 'bar%s' % i}) with self.table.batch_writer() as batch: for item in items: batch.put_item(Item=item) diff --git a/tests/integration/test_s3.py b/tests/integration/test_s3.py index fa5191b..5393bbc 100644 --- a/tests/integration/test_s3.py +++ b/tests/integration/test_s3.py @@ -10,23 +10,22 @@ # 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. -import os -import threading -import math -import tempfile -import shutil -import hashlib -import string import datetime +import hashlib import logging +import math +import os +import shutil +import string +import tempfile +import threading -from tests import unittest, unique_id -from botocore.compat import six from botocore.client import Config +from botocore.compat import six -import boto3.session import boto3.s3.transfer - +import boto3.session +from tests import unique_id, unittest urlopen = six.moves.urllib.request.urlopen LOG = logging.getLogger('boto3.tests.integration') @@ -34,13 +33,15 @@ LOG = logging.getLogger('boto3.tests.integration') def assert_files_equal(first, second): if os.path.getsize(first) != os.path.getsize(second): - raise AssertionError("Files are not equal: %s, %s" % (first, second)) + raise AssertionError(f"Files are not equal: {first}, {second}") first_md5 = md5_checksum(first) second_md5 = md5_checksum(second) if first_md5 != second_md5: raise AssertionError( - "Files are not equal: %s(md5=%s) != %s(md5=%s)" % ( - first, first_md5, second, second_md5)) + "Files are not equal: {}(md5={}) != {}(md5={})".format( + first, first_md5, second, second_md5 + ) + ) def md5_checksum(filename): @@ -68,7 +69,7 @@ def setup_module(): 'Bucket': _SHARED_BUCKET, 'CreateBucketConfiguration': { 'LocationConstraint': _DEFAULT_REGION, - } + }, } try: s3.create_bucket(**params) @@ -96,8 +97,9 @@ def clear_out_bucket(bucket, region, delete_bucket=False): # delete a bucket. We'll let the waiter make # the final call as to whether the bucket was able # to be deleted. - LOG.debug("delete_bucket() raised an exception: %s", - e, exc_info=True) + LOG.debug( + "delete_bucket() raised an exception: %s", e, exc_info=True + ) waiter = s3.get_waiter('bucket_not_exists') waiter.wait(Bucket=bucket) @@ -106,7 +108,7 @@ def teardown_module(): clear_out_bucket(_SHARED_BUCKET, _DEFAULT_REGION, delete_bucket=True) -class FileCreator(object): +class FileCreator: def __init__(self): self.rootdir = tempfile.mkdtemp() @@ -197,8 +199,7 @@ class TestS3Resource(unittest.TestCase): # Create an object obj = self.bucket.Object('test.txt') - obj.put( - Body='hello, world') + obj.put(Body='hello, world') waiter = client.get_waiter('object_exists') waiter.wait(Bucket=self.bucket_name, Key='test.txt') self.addCleanup(obj.delete) @@ -213,8 +214,7 @@ class TestS3Resource(unittest.TestCase): self.assertEqual(12, list(self.bucket.objects.all())[0].size) # Perform a resource action with a low-level response - self.assertEqual(b'hello, world', - obj.get()['Body'].read()) + self.assertEqual(b'hello, world', obj.get()['Body'].read()) def test_s3_resource_waiter(self): # Create a bucket @@ -224,13 +224,11 @@ class TestS3Resource(unittest.TestCase): bucket.wait_until_exists() # Confirm the bucket exists by finding it in a list of all of our # buckets - self.assertIn(bucket_name, - [b.name for b in self.s3.buckets.all()]) + self.assertIn(bucket_name, [b.name for b in self.s3.buckets.all()]) # Create an object obj = bucket.Object('test.txt') - obj.put( - Body='hello, world') + obj.put(Body='hello, world') self.addCleanup(obj.delete) # Wait till the bucket exists @@ -255,14 +253,7 @@ class TestS3Resource(unittest.TestCase): response = part.upload(Body='hello, world!') # Complete the upload, which requires info on all of the parts - part_info = { - 'Parts': [ - { - 'PartNumber': 1, - 'ETag': response['ETag'] - } - ] - } + part_info = {'Parts': [{'PartNumber': 1, 'ETag': response['ETag']}]} mpu.complete(MultipartUpload=part_info) self.addCleanup(self.bucket.Object('mp-test.txt').delete) @@ -279,7 +270,7 @@ class TestS3Resource(unittest.TestCase): for i in range(10): obj.put(Body="Version %s" % i) - # Delete all the versions of the object + # Delete all the versions of the object bucket.object_versions.all().delete() versions = list(bucket.object_versions.all()) @@ -288,6 +279,7 @@ class TestS3Resource(unittest.TestCase): class TestS3Transfers(unittest.TestCase): """Tests for the high level boto3.s3.transfer module.""" + def setUp(self): self.region = _DEFAULT_REGION self.bucket_name = _SHARED_BUCKET @@ -301,17 +293,16 @@ class TestS3Transfers(unittest.TestCase): self.files.remove_all() def delete_object(self, key): - self.client.delete_object( - Bucket=self.bucket_name, - Key=key) + self.client.delete_object(Bucket=self.bucket_name, Key=key) def object_exists(self, key): waiter = self.client.get_waiter('object_exists') waiter.wait(Bucket=self.bucket_name, Key=key) return True - def wait_until_object_exists(self, key_name, extra_params=None, - min_successes=3): + def wait_until_object_exists( + self, key_name, extra_params=None, min_successes=3 + ): waiter = self.client.get_waiter('object_exists') params = {'Bucket': self.bucket_name, 'Key': key_name} if extra_params is not None: @@ -320,23 +311,27 @@ class TestS3Transfers(unittest.TestCase): waiter.wait(**params) def create_s3_transfer(self, config=None): - return boto3.s3.transfer.S3Transfer(self.client, - config=config) + return boto3.s3.transfer.S3Transfer(self.client, config=config) def assert_has_public_read_acl(self, response): grants = response['Grants'] - public_read = [g['Grantee'].get('URI', '') for g in grants - if g['Permission'] == 'READ'] + public_read = [ + g['Grantee'].get('URI', '') + for g in grants + if g['Permission'] == 'READ' + ] self.assertIn('groups/global/AllUsers', public_read[0]) def test_copy(self): self.client.put_object( - Bucket=self.bucket_name, Key='foo', Body='beach') + Bucket=self.bucket_name, Key='foo', Body='beach' + ) self.addCleanup(self.delete_object, 'foo') self.client.copy( CopySource={'Bucket': self.bucket_name, 'Key': 'foo'}, - Bucket=self.bucket_name, Key='bar' + Bucket=self.bucket_name, + Key='bar', ) self.addCleanup(self.delete_object, 'bar') @@ -345,7 +340,8 @@ class TestS3Transfers(unittest.TestCase): def test_upload_fileobj(self): fileobj = six.BytesIO(b'foo') self.client.upload_fileobj( - Fileobj=fileobj, Bucket=self.bucket_name, Key='foo') + Fileobj=fileobj, Bucket=self.bucket_name, Key='foo' + ) self.addCleanup(self.delete_object, 'foo') self.object_exists('foo') @@ -354,11 +350,11 @@ class TestS3Transfers(unittest.TestCase): # This has to be an integration test because the fileobj will never # actually be read from when using the stubber and therefore the # progress callbacks will not be invoked. - chunksize = 5 * (1024 ** 2) + chunksize = 5 * (1024**2) config = boto3.s3.transfer.TransferConfig( multipart_chunksize=chunksize, multipart_threshold=chunksize, - max_concurrency=1 + max_concurrency=1, ) fileobj = six.BytesIO(b'0' * (chunksize * 3)) @@ -366,8 +362,12 @@ class TestS3Transfers(unittest.TestCase): self.progress += amount self.client.upload_fileobj( - Fileobj=fileobj, Bucket=self.bucket_name, Key='foo', - Config=config, Callback=progress_callback) + Fileobj=fileobj, + Bucket=self.bucket_name, + Key='foo', + Config=config, + Callback=progress_callback, + ) self.addCleanup(self.delete_object, 'foo') self.object_exists('foo') @@ -376,52 +376,60 @@ class TestS3Transfers(unittest.TestCase): def test_download_fileobj(self): fileobj = six.BytesIO() self.client.put_object( - Bucket=self.bucket_name, Key='foo', Body=b'beach') + Bucket=self.bucket_name, Key='foo', Body=b'beach' + ) self.addCleanup(self.delete_object, 'foo') self.wait_until_object_exists('foo') self.client.download_fileobj( - Bucket=self.bucket_name, Key='foo', Fileobj=fileobj) + Bucket=self.bucket_name, Key='foo', Fileobj=fileobj + ) self.assertEqual(fileobj.getvalue(), b'beach') def test_upload_below_threshold(self): config = boto3.s3.transfer.TransferConfig( - multipart_threshold=2 * 1024 * 1024) + multipart_threshold=2 * 1024 * 1024 + ) transfer = self.create_s3_transfer(config) filename = self.files.create_file_with_size( - 'foo.txt', filesize=1024 * 1024) - transfer.upload_file(filename, self.bucket_name, - 'foo.txt') + 'foo.txt', filesize=1024 * 1024 + ) + transfer.upload_file(filename, self.bucket_name, 'foo.txt') self.addCleanup(self.delete_object, 'foo.txt') self.assertTrue(self.object_exists('foo.txt')) def test_upload_above_threshold(self): config = boto3.s3.transfer.TransferConfig( - multipart_threshold=2 * 1024 * 1024) + multipart_threshold=2 * 1024 * 1024 + ) transfer = self.create_s3_transfer(config) filename = self.files.create_file_with_size( - '20mb.txt', filesize=20 * 1024 * 1024) - transfer.upload_file(filename, self.bucket_name, - '20mb.txt') + '20mb.txt', filesize=20 * 1024 * 1024 + ) + transfer.upload_file(filename, self.bucket_name, '20mb.txt') self.addCleanup(self.delete_object, '20mb.txt') self.assertTrue(self.object_exists('20mb.txt')) def test_upload_file_above_threshold_with_acl(self): config = boto3.s3.transfer.TransferConfig( - multipart_threshold=5 * 1024 * 1024) + multipart_threshold=5 * 1024 * 1024 + ) transfer = self.create_s3_transfer(config) filename = self.files.create_file_with_size( - '6mb.txt', filesize=6 * 1024 * 1024) + '6mb.txt', filesize=6 * 1024 * 1024 + ) extra_args = {'ACL': 'public-read'} - transfer.upload_file(filename, self.bucket_name, - '6mb.txt', extra_args=extra_args) + transfer.upload_file( + filename, self.bucket_name, '6mb.txt', extra_args=extra_args + ) self.addCleanup(self.delete_object, '6mb.txt') self.assertTrue(self.object_exists('6mb.txt')) response = self.client.get_object_acl( - Bucket=self.bucket_name, Key='6mb.txt') + Bucket=self.bucket_name, Key='6mb.txt' + ) self.assert_has_public_read_acl(response) def test_upload_file_above_threshold_with_ssec(self): @@ -431,19 +439,22 @@ class TestS3Transfers(unittest.TestCase): 'SSECustomerAlgorithm': 'AES256', } config = boto3.s3.transfer.TransferConfig( - multipart_threshold=5 * 1024 * 1024) + multipart_threshold=5 * 1024 * 1024 + ) transfer = self.create_s3_transfer(config) filename = self.files.create_file_with_size( - '6mb.txt', filesize=6 * 1024 * 1024) - transfer.upload_file(filename, self.bucket_name, - '6mb.txt', extra_args=extra_args) + '6mb.txt', filesize=6 * 1024 * 1024 + ) + transfer.upload_file( + filename, self.bucket_name, '6mb.txt', extra_args=extra_args + ) self.addCleanup(self.delete_object, '6mb.txt') # A head object will fail if it has a customer key # associated with it and it's not provided in the HeadObject # request so we can use this to verify our functionality. response = self.client.head_object( - Bucket=self.bucket_name, - Key='6mb.txt', **extra_args) + Bucket=self.bucket_name, Key='6mb.txt', **extra_args + ) self.assertEqual(response['SSECustomerAlgorithm'], 'AES256') def test_progress_callback_on_upload(self): @@ -456,9 +467,11 @@ class TestS3Transfers(unittest.TestCase): transfer = self.create_s3_transfer() filename = self.files.create_file_with_size( - '20mb.txt', filesize=20 * 1024 * 1024) - transfer.upload_file(filename, self.bucket_name, - '20mb.txt', callback=progress_callback) + '20mb.txt', filesize=20 * 1024 * 1024 + ) + transfer.upload_file( + filename, self.bucket_name, '20mb.txt', callback=progress_callback + ) self.addCleanup(self.delete_object, '20mb.txt') # The callback should have been called enough times such that @@ -478,13 +491,15 @@ class TestS3Transfers(unittest.TestCase): self.amount_seen += amount client = self.session.client( - 's3', self.region, - config=Config(signature_version='s3v4')) + 's3', self.region, config=Config(signature_version='s3v4') + ) transfer = boto3.s3.transfer.S3Transfer(client) filename = self.files.create_file_with_size( - '10mb.txt', filesize=10 * 1024 * 1024) - transfer.upload_file(filename, self.bucket_name, - '10mb.txt', callback=progress_callback) + '10mb.txt', filesize=10 * 1024 * 1024 + ) + transfer.upload_file( + filename, self.bucket_name, '10mb.txt', callback=progress_callback + ) self.addCleanup(self.delete_object, '10mb.txt') self.assertEqual(self.amount_seen, 10 * 1024 * 1024) @@ -492,12 +507,17 @@ class TestS3Transfers(unittest.TestCase): def test_can_send_extra_params_on_upload(self): transfer = self.create_s3_transfer() filename = self.files.create_file_with_size('foo.txt', filesize=1024) - transfer.upload_file(filename, self.bucket_name, - 'foo.txt', extra_args={'ACL': 'public-read'}) + transfer.upload_file( + filename, + self.bucket_name, + 'foo.txt', + extra_args={'ACL': 'public-read'}, + ) self.addCleanup(self.delete_object, 'foo.txt') response = self.client.get_object_acl( - Bucket=self.bucket_name, Key='foo.txt') + Bucket=self.bucket_name, Key='foo.txt' + ) self.assert_has_public_read_acl(response) def test_can_configure_threshold(self): @@ -506,9 +526,9 @@ class TestS3Transfers(unittest.TestCase): ) transfer = self.create_s3_transfer(config) filename = self.files.create_file_with_size( - 'foo.txt', filesize=8 * 1024 * 1024) - transfer.upload_file(filename, self.bucket_name, - 'foo.txt') + 'foo.txt', filesize=8 * 1024 * 1024 + ) + transfer.upload_file(filename, self.bucket_name, 'foo.txt') self.addCleanup(self.delete_object, 'foo.txt') self.assertTrue(self.object_exists('foo.txt')) @@ -522,17 +542,20 @@ class TestS3Transfers(unittest.TestCase): 'SSECustomerKey': key_bytes, 'SSECustomerAlgorithm': 'AES256', } - self.client.put_object(Bucket=self.bucket_name, - Key='foo.txt', - Body=b'hello world', - **extra_args) + self.client.put_object( + Bucket=self.bucket_name, + Key='foo.txt', + Body=b'hello world', + **extra_args, + ) self.addCleanup(self.delete_object, 'foo.txt') transfer = self.create_s3_transfer() download_path = os.path.join(self.files.rootdir, 'downloaded.txt') self.wait_until_object_exists('foo.txt', extra_params=extra_args) - transfer.download_file(self.bucket_name, 'foo.txt', - download_path, extra_args=extra_args) + transfer.download_file( + self.bucket_name, 'foo.txt', download_path, extra_args=extra_args + ) with open(download_path, 'rb') as f: self.assertEqual(f.read(), b'hello world') @@ -546,15 +569,21 @@ class TestS3Transfers(unittest.TestCase): transfer = self.create_s3_transfer() filename = self.files.create_file_with_size( - '20mb.txt', filesize=20 * 1024 * 1024) + '20mb.txt', filesize=20 * 1024 * 1024 + ) with open(filename, 'rb') as f: - self.client.put_object(Bucket=self.bucket_name, - Key='20mb.txt', Body=f) + self.client.put_object( + Bucket=self.bucket_name, Key='20mb.txt', Body=f + ) self.addCleanup(self.delete_object, '20mb.txt') download_path = os.path.join(self.files.rootdir, 'downloaded.txt') - transfer.download_file(self.bucket_name, '20mb.txt', - download_path, callback=progress_callback) + transfer.download_file( + self.bucket_name, + '20mb.txt', + download_path, + callback=progress_callback, + ) self.assertEqual(self.amount_seen, 20 * 1024 * 1024) @@ -562,46 +591,45 @@ class TestS3Transfers(unittest.TestCase): transfer = self.create_s3_transfer() filename = self.files.create_file_with_size( - 'foo.txt', filesize=1024 * 1024) + 'foo.txt', filesize=1024 * 1024 + ) with open(filename, 'rb') as f: - self.client.put_object(Bucket=self.bucket_name, - Key='foo.txt', - Body=f) + self.client.put_object( + Bucket=self.bucket_name, Key='foo.txt', Body=f + ) self.addCleanup(self.delete_object, 'foo.txt') download_path = os.path.join(self.files.rootdir, 'downloaded.txt') self.wait_until_object_exists('foo.txt') - transfer.download_file(self.bucket_name, 'foo.txt', - download_path) + transfer.download_file(self.bucket_name, 'foo.txt', download_path) assert_files_equal(filename, download_path) def test_download_above_threshold(self): transfer = self.create_s3_transfer() filename = self.files.create_file_with_size( - 'foo.txt', filesize=20 * 1024 * 1024) + 'foo.txt', filesize=20 * 1024 * 1024 + ) with open(filename, 'rb') as f: - self.client.put_object(Bucket=self.bucket_name, - Key='foo.txt', - Body=f) + self.client.put_object( + Bucket=self.bucket_name, Key='foo.txt', Body=f + ) self.addCleanup(self.delete_object, 'foo.txt') download_path = os.path.join(self.files.rootdir, 'downloaded.txt') self.wait_until_object_exists('foo.txt') - transfer.download_file(self.bucket_name, 'foo.txt', - download_path) + transfer.download_file(self.bucket_name, 'foo.txt', download_path) assert_files_equal(filename, download_path) def test_download_file_with_directory_not_exist(self): transfer = self.create_s3_transfer() self.client.put_object( - Bucket=self.bucket_name, - Key='foo.txt', - Body=b'foo' + Bucket=self.bucket_name, Key='foo.txt', Body=b'foo' ) self.addCleanup(self.delete_object, 'foo.txt') - download_path = os.path.join(self.files.rootdir, 'a', 'b', 'c', - 'downloaded.txt') + download_path = os.path.join( + self.files.rootdir, 'a', 'b', 'c', 'downloaded.txt' + ) self.wait_until_object_exists('foo.txt') with self.assertRaises(IOError): transfer.download_file(self.bucket_name, 'foo.txt', download_path) @@ -610,14 +638,16 @@ class TestS3Transfers(unittest.TestCase): transfer = self.create_s3_transfer() filename = self.files.create_file_with_size( - 'foo.txt', filesize=20 * 1024 * 1024) + 'foo.txt', filesize=20 * 1024 * 1024 + ) with open(filename, 'rb') as f: - self.client.put_object(Bucket=self.bucket_name, - Key='foo.txt', - Body=f) + self.client.put_object( + Bucket=self.bucket_name, Key='foo.txt', Body=f + ) self.addCleanup(self.delete_object, 'foo.txt') - download_path = os.path.join(self.files.rootdir, 'a', 'b', 'c', - 'downloaded.txt') + download_path = os.path.join( + self.files.rootdir, 'a', 'b', 'c', 'downloaded.txt' + ) self.wait_until_object_exists('foo.txt') with self.assertRaises(IOError): transfer.download_file(self.bucket_name, 'foo.txt', download_path) @@ -627,17 +657,18 @@ class TestS3Transfers(unittest.TestCase): # from the clients work. We're not exhaustively testing through # this client interface. filename = self.files.create_file_with_size( - 'foo.txt', filesize=1024 * 1024) - self.client.upload_file(Filename=filename, - Bucket=self.bucket_name, - Key='foo.txt') + 'foo.txt', filesize=1024 * 1024 + ) + self.client.upload_file( + Filename=filename, Bucket=self.bucket_name, Key='foo.txt' + ) self.addCleanup(self.delete_object, 'foo.txt') download_path = os.path.join(self.files.rootdir, 'downloaded.txt') self.wait_until_object_exists('foo.txt') - self.client.download_file(Bucket=self.bucket_name, - Key='foo.txt', - Filename=download_path) + self.client.download_file( + Bucket=self.bucket_name, Key='foo.txt', Filename=download_path + ) assert_files_equal(filename, download_path) def test_transfer_methods_do_not_use_threads(self): @@ -656,14 +687,15 @@ class TestS3Transfers(unittest.TestCase): config = boto3.s3.transfer.TransferConfig(use_threads=False) self.client.upload_file( - Bucket=self.bucket_name, Key=key, Filename=filename, - Config=config) + Bucket=self.bucket_name, Key=key, Filename=filename, Config=config + ) self.addCleanup(self.delete_object, key) self.assertTrue(self.object_exists(key)) fileobj = six.BytesIO() self.client.download_fileobj( - Bucket=self.bucket_name, Key='foo', Fileobj=fileobj, Config=config) + Bucket=self.bucket_name, Key='foo', Fileobj=fileobj, Config=config + ) self.assertEqual(fileobj.getvalue(), content) def test_transfer_methods_through_bucket(self): diff --git a/tests/integration/test_session.py b/tests/integration/test_session.py index 445c16f..87f6bff 100644 --- a/tests/integration/test_session.py +++ b/tests/integration/test_session.py @@ -10,20 +10,22 @@ # 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. -from tests import unittest import botocore.session import boto3.session +from tests import unittest class TestUserAgentCustomizations(unittest.TestCase): def setUp(self): self.botocore_session = botocore.session.get_session() self.session = boto3.session.Session( - region_name='us-west-2', botocore_session=self.botocore_session) + region_name='us-west-2', botocore_session=self.botocore_session + ) self.actual_user_agent = None - self.botocore_session.register('request-created', - self.record_user_agent) + self.botocore_session.register( + 'request-created', self.record_user_agent + ) def record_user_agent(self, request, **kwargs): self.actual_user_agent = request.headers['User-Agent'] diff --git a/tests/integration/test_sqs.py b/tests/integration/test_sqs.py index 7fbfb9a..7a8cbe1 100644 --- a/tests/integration/test_sqs.py +++ b/tests/integration/test_sqs.py @@ -12,8 +12,7 @@ # language governing permissions and limitations under the License. import boto3.session - -from tests import unittest, unique_id +from tests import unique_id, unittest class TestSQSResource(unittest.TestCase): diff --git a/tests/unit/docs/__init__.py b/tests/unit/docs/__init__.py index 4f73c2a..753275e 100644 --- a/tests/unit/docs/__init__.py +++ b/tests/unit/docs/__init__.py @@ -10,36 +10,41 @@ # 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. -import os import json -import tempfile +import os import shutil -from tests import unittest +import tempfile import botocore.session from botocore.compat import OrderedDict -from botocore.loaders import Loader from botocore.docs.bcdoc.restdoc import DocumentStructure +from botocore.loaders import Loader from boto3.session import Session +from tests import unittest class BaseDocsTest(unittest.TestCase): def setUp(self): self.root_dir = tempfile.mkdtemp() self.version_dirs = os.path.join( - self.root_dir, 'myservice', '2014-01-01') + self.root_dir, 'myservice', '2014-01-01' + ) os.makedirs(self.version_dirs) self.model_file = os.path.join(self.version_dirs, 'service-2.json') self.waiter_model_file = os.path.join( - self.version_dirs, 'waiters-2.json') + self.version_dirs, 'waiters-2.json' + ) self.paginator_model_file = os.path.join( - self.version_dirs, 'paginators-1.json') + self.version_dirs, 'paginators-1.json' + ) self.resource_model_file = os.path.join( - self.version_dirs, 'resources-1.json') + self.version_dirs, 'resources-1.json' + ) self.example_model_file = os.path.join( - self.version_dirs, 'examples-1.json') + self.version_dirs, 'examples-1.json' + ) self.json_model = {} self.waiter_json_model = {} @@ -61,7 +66,8 @@ class BaseDocsTest(unittest.TestCase): self.botocore_session = botocore.session.get_session() self.botocore_session.register_component('data_loader', self.loader) self.session = Session( - botocore_session=self.botocore_session, region_name='us-east-1') + botocore_session=self.botocore_session, region_name='us-east-1' + ) self.client = self.session.client('myservice', 'us-east-1') self.resource = self.session.resource('myservice', 'us-east-1') @@ -79,44 +85,54 @@ class BaseDocsTest(unittest.TestCase): 'SampleOperation': { 'name': 'SampleOperation', 'input': {'shape': 'SampleOperationInputOutput'}, - 'output': {'shape': 'SampleOperationInputOutput'} + 'output': {'shape': 'SampleOperationInputOutput'}, } }, 'shapes': { 'SampleOperationInputOutput': { 'type': 'structure', - 'members': OrderedDict([ - ('Foo', { - 'shape': 'String', - 'documentation': 'Documents Foo'}), - ('Bar', { - 'shape': 'String', - 'documentation': 'Documents Bar'}), - ]) + 'members': OrderedDict( + [ + ( + 'Foo', + { + 'shape': 'String', + 'documentation': 'Documents Foo', + }, + ), + ( + 'Bar', + { + 'shape': 'String', + 'documentation': 'Documents Bar', + }, + ), + ] + ), }, - 'String': { - 'type': 'string' - } - } + 'String': {'type': 'string'}, + }, } self.example_json_model = { "version": 1, "examples": { - "SampleOperation": [{ - "id": "sample-id", - "title": "sample-title", - "description": "Sample Description.", - "input": OrderedDict([ - ("Foo", "bar"), - ]), - "comments": { - "input": { - "Foo": "biz" + "SampleOperation": [ + { + "id": "sample-id", + "title": "sample-title", + "description": "Sample Description.", + "input": OrderedDict( + [ + ("Foo", "bar"), + ] + ), + "comments": { + "input": {"Foo": "biz"}, }, } - }] - } + ] + }, } self.waiter_json_model = { @@ -127,17 +143,21 @@ class BaseDocsTest(unittest.TestCase): "operation": "SampleOperation", "maxAttempts": 40, "acceptors": [ - {"expected": "complete", - "matcher": "pathAll", - "state": "success", - "argument": "Biz"}, - {"expected": "failed", - "matcher": "pathAny", - "state": "failure", - "argument": "Biz"} - ] + { + "expected": "complete", + "matcher": "pathAll", + "state": "success", + "argument": "Biz", + }, + { + "expected": "failed", + "matcher": "pathAny", + "state": "failure", + "argument": "Biz", + }, + ], } - } + }, } self.paginator_json_model = { @@ -146,36 +166,45 @@ class BaseDocsTest(unittest.TestCase): "input_token": "NextResult", "output_token": "NextResult", "limit_key": "MaxResults", - "result_key": "Biz" + "result_key": "Biz", } } } self.resource_json_model = { "service": { - "actions": OrderedDict([ - ("SampleOperation", { - "request": {"operation": "SampleOperation"} - }), - ("SampleListReturnOperation", { - "request": {"operation": "SampleOperation"}, - "resource": { - "type": "Sample", - "identifiers": [ - {"target": "Name", "source": "response", - "path": "Samples[].Name"} - ], - "path": "Samples[]" - } - }) - ]), + "actions": OrderedDict( + [ + ( + "SampleOperation", + {"request": {"operation": "SampleOperation"}}, + ), + ( + "SampleListReturnOperation", + { + "request": {"operation": "SampleOperation"}, + "resource": { + "type": "Sample", + "identifiers": [ + { + "target": "Name", + "source": "response", + "path": "Samples[].Name", + } + ], + "path": "Samples[]", + }, + }, + ), + ] + ), "has": { "Sample": { "resource": { "type": "Sample", "identifiers": [ {"target": "Name", "source": "input"} - ] + ], } } }, @@ -185,26 +214,30 @@ class BaseDocsTest(unittest.TestCase): "resource": { "type": "Sample", "identifiers": [ - {"target": "Name", "source": "response", - "path": "Samples[].Foo"} - ] - } + { + "target": "Name", + "source": "response", + "path": "Samples[].Foo", + } + ], + }, } - } + }, }, "resources": { "Sample": { - "identifiers": [ - {"name": "Name", "memberName": "Foo"} - ], + "identifiers": [{"name": "Name", "memberName": "Foo"}], "shape": "SampleOperationInputOutput", "load": { "request": { "operation": "SampleOperation", "params": [ - {"target": "Foo", "source": "identifier", - "name": "Name"} - ] + { + "target": "Foo", + "source": "identifier", + "name": "Name", + } + ], } }, "actions": { @@ -212,9 +245,12 @@ class BaseDocsTest(unittest.TestCase): "request": { "operation": "SampleOperation", "params": [ - {"target": "Foo", "source": "identifier", - "name": "Name"} - ] + { + "target": "Foo", + "source": "identifier", + "name": "Name", + } + ], } } }, @@ -223,9 +259,12 @@ class BaseDocsTest(unittest.TestCase): "request": { "operation": "SampleOperation", "params": [ - {"target": "Samples[].Foo", - "source": "identifier", "name": "Name"} - ] + { + "target": "Samples[].Foo", + "source": "identifier", + "name": "Name", + } + ], } } }, @@ -234,9 +273,12 @@ class BaseDocsTest(unittest.TestCase): "resource": { "type": "Sample", "identifiers": [ - {"target": "Name", "source": "data", - "path": "Foo"} - ] + { + "target": "Name", + "source": "data", + "path": "Foo", + } + ], } } }, @@ -244,13 +286,16 @@ class BaseDocsTest(unittest.TestCase): "Complete": { "waiterName": "SampleOperationComplete", "params": [ - {"target": "Foo", "source": "identifier", - "name": "Name"} - ] + { + "target": "Foo", + "source": "identifier", + "name": "Name", + } + ], } - } + }, } - } + }, } def _write_models(self): @@ -273,8 +318,9 @@ class BaseDocsTest(unittest.TestCase): shape_name = list(shape.keys())[0] self.json_model['shapes'][shape_name] = shape[shape_name] - def add_shape_to_params(self, param_name, shape_name, documentation=None, - is_required=False): + def add_shape_to_params( + self, param_name, shape_name, documentation=None, is_required=False + ): params_shape = self.json_model['shapes']['SampleOperationInputOutput'] member = {'shape': shape_name} if documentation is not None: @@ -292,7 +338,7 @@ class BaseDocsTest(unittest.TestCase): for line in lines: assert line in contents beginning = contents.find(line) - contents = contents[(beginning + len(line)):] + contents = contents[(beginning + len(line)) :] def assert_not_contains_lines(self, lines): contents = self.doc_structure.flush_structure().decode('utf-8') diff --git a/tests/unit/docs/test_action.py b/tests/unit/docs/test_action.py index 67ef2c9..e0e6194 100644 --- a/tests/unit/docs/test_action.py +++ b/tests/unit/docs/test_action.py @@ -10,79 +10,86 @@ # 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. -from tests.unit.docs import BaseDocsTest - from boto3.docs.action import ActionDocumenter +from tests.unit.docs import BaseDocsTest class TestActionDocumenter(BaseDocsTest): def test_document_service_resource_actions(self): action_documenter = ActionDocumenter(self.resource) action_documenter.document_actions(self.doc_structure) - self.assert_contains_lines_in_order([ - '.. py:method:: sample_operation(**kwargs)', - ' **Request Syntax**', - ' ::', - ' response = myservice.sample_operation(', - ' Foo=\'string\',', - ' Bar=\'string\'', - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns:', - ' **Response Syntax**', - ' ::', - ' {', - ' \'Foo\': \'string\',', - ' \'Bar\': \'string\'', - ' }', - ' **Response Structure**', - ' - *(dict) --*', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar' - ]) + self.assert_contains_lines_in_order( + [ + '.. py:method:: sample_operation(**kwargs)', + ' **Request Syntax**', + ' ::', + ' response = myservice.sample_operation(', + ' Foo=\'string\',', + ' Bar=\'string\'', + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns:', + ' **Response Syntax**', + ' ::', + ' {', + ' \'Foo\': \'string\',', + ' \'Bar\': \'string\'', + ' }', + ' **Response Structure**', + ' - *(dict) --*', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ] + ) def test_document_nonservice_resource_actions(self): subresource = self.resource.Sample('mysample') action_documenter = ActionDocumenter(subresource) action_documenter.document_actions(self.doc_structure) - self.assert_contains_lines_in_order([ - '.. py:method:: load()', - (' Calls :py:meth:`MyService.Client.sample_operation` to update ' - 'the attributes of the Sample resource'), - ' **Request Syntax** ', - ' ::', - ' sample.load()', - ' :returns: None', - '.. py:method:: operate(**kwargs)', - ' **Request Syntax** ', - ' ::', - ' response = sample.operate(', - " Bar='string'", - ' )', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns: ', - ' ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar', - '.. py:method:: reload()', - (' Calls :py:meth:`MyService.Client.sample_operation` to update ' - 'the attributes of the Sample resource'), - ' **Request Syntax** ', - ' ::', - ' sample.reload()', - ' :returns: None' - ]) + self.assert_contains_lines_in_order( + [ + '.. py:method:: load()', + ( + ' Calls :py:meth:`MyService.Client.sample_operation` to update ' + 'the attributes of the Sample resource' + ), + ' **Request Syntax** ', + ' ::', + ' sample.load()', + ' :returns: None', + '.. py:method:: operate(**kwargs)', + ' **Request Syntax** ', + ' ::', + ' response = sample.operate(', + " Bar='string'", + ' )', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns: ', + ' ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + '.. py:method:: reload()', + ( + ' Calls :py:meth:`MyService.Client.sample_operation` to update ' + 'the attributes of the Sample resource' + ), + ' **Request Syntax** ', + ' ::', + ' sample.reload()', + ' :returns: None', + ] + ) diff --git a/tests/unit/docs/test_attr.py b/tests/unit/docs/test_attr.py index 280a226..aef224a 100644 --- a/tests/unit/docs/test_attr.py +++ b/tests/unit/docs/test_attr.py @@ -12,39 +12,43 @@ # language governing permissions and limitations under the License. from botocore.hooks import HierarchicalEmitter -from tests.unit.docs import BaseDocsTest from boto3.docs.attr import document_attribute +from tests.unit.docs import BaseDocsTest class TestDocumentAttribute(BaseDocsTest): def setUp(self): - super(TestDocumentAttribute, self).setUp() - self.add_shape({ - 'NestedStruct': { - 'type': 'structure', - 'members': { - 'NestedStrAttr': { - 'shape': 'String', - 'documentation': 'Documents a nested string attribute' - } - } - } - }) - self.add_shape({ - 'ResourceShape': { - 'type': 'structure', - 'members': { - 'StringAttr': { - 'shape': 'String', - 'documentation': 'Documents a string attribute' + super().setUp() + self.add_shape( + { + 'NestedStruct': { + 'type': 'structure', + 'members': { + 'NestedStrAttr': { + 'shape': 'String', + 'documentation': 'Documents a nested string attribute', + } }, - 'NestedAttr': { - 'shape': 'NestedStruct', - 'documentation': 'Documents a nested attribute' - } } } - }) + ) + self.add_shape( + { + 'ResourceShape': { + 'type': 'structure', + 'members': { + 'StringAttr': { + 'shape': 'String', + 'documentation': 'Documents a string attribute', + }, + 'NestedAttr': { + 'shape': 'NestedStruct', + 'documentation': 'Documents a nested attribute', + }, + }, + } + } + ) self.setup_client_and_resource() self.event_emitter = HierarchicalEmitter() @@ -56,22 +60,38 @@ class TestDocumentAttribute(BaseDocsTest): shape_model = self.service_model.shape_for('ResourceShape') attr_name = 'StringAttr' document_attribute( - self.doc_structure, self.service_name, self.resource_name, - attr_name, self.event_emitter, shape_model.members[attr_name]) - self.assert_contains_lines_in_order([ - '.. py:attribute:: StringAttr', - ' - *(string) --* Documents a string attribute' - ]) + self.doc_structure, + self.service_name, + self.resource_name, + attr_name, + self.event_emitter, + shape_model.members[attr_name], + ) + self.assert_contains_lines_in_order( + [ + '.. py:attribute:: StringAttr', + ' - *(string) --* Documents a string attribute', + ] + ) def test_document_attr_structure(self): shape_model = self.service_model.shape_for('ResourceShape') attr_name = 'NestedAttr' document_attribute( - self.doc_structure, self.service_name, self.resource_name, - attr_name, self.event_emitter, shape_model.members[attr_name]) - self.assert_contains_lines_in_order([ - '.. py:attribute:: NestedAttr', - ' - *(dict) --* Documents a nested attribute', - (' - **NestedStrAttr** *(string) --* Documents a nested ' - 'string attribute') - ]) + self.doc_structure, + self.service_name, + self.resource_name, + attr_name, + self.event_emitter, + shape_model.members[attr_name], + ) + self.assert_contains_lines_in_order( + [ + '.. py:attribute:: NestedAttr', + ' - *(dict) --* Documents a nested attribute', + ( + ' - **NestedStrAttr** *(string) --* Documents a nested ' + 'string attribute' + ), + ] + ) diff --git a/tests/unit/docs/test_client.py b/tests/unit/docs/test_client.py index a4c9367..f94af36 100644 --- a/tests/unit/docs/test_client.py +++ b/tests/unit/docs/test_client.py @@ -10,58 +10,57 @@ # 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. -from tests.unit.docs import BaseDocsTest - from boto3.docs.client import Boto3ClientDocumenter +from tests.unit.docs import BaseDocsTest class TestBoto3ClientDocumenter(BaseDocsTest): def setUp(self): - super(TestBoto3ClientDocumenter, self).setUp() + super().setUp() self.client_documenter = Boto3ClientDocumenter(self.client) def test_document_client(self): self.client_documenter.document_client(self.doc_structure) - self.assert_contains_lines_in_order([ - '======', - 'Client', - '======', - '.. py:class:: MyService.Client', - ' A low-level client representing AWS MyService', - ' ::', - ' import boto3', - ' client = boto3.client(\'myservice\')', - ' These are the available methods:', - ' * :py:meth:`~MyService.Client.can_paginate`', - ' * :py:meth:`~MyService.Client.get_paginator`', - ' * :py:meth:`~MyService.Client.get_waiter`', - ' * :py:meth:`~MyService.Client.sample_operation`', - ' .. py:method:: can_paginate(operation_name)', - ' .. py:method:: get_paginator(operation_name)', - ' .. py:method:: get_waiter(waiter_name)', - ' .. py:method:: sample_operation(**kwargs)', - ' **Request Syntax**', - ' ::', - ' response = client.sample_operation(', - ' Foo=\'string\'', - ' Bar=\'string\'', - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns:', - ' **Response Syntax**', - ' ::', - ' {', - ' \'Foo\': \'string\'', - ' \'Bar\': \'string\'', - - ' }', - ' **Response Structure**', - ' - *(dict) --*', - ' - **Foo** *(string) --*', - ' - **Bar** *(string) --*' - - ]) + self.assert_contains_lines_in_order( + [ + '======', + 'Client', + '======', + '.. py:class:: MyService.Client', + ' A low-level client representing AWS MyService', + ' ::', + ' import boto3', + ' client = boto3.client(\'myservice\')', + ' These are the available methods:', + ' * :py:meth:`~MyService.Client.can_paginate`', + ' * :py:meth:`~MyService.Client.get_paginator`', + ' * :py:meth:`~MyService.Client.get_waiter`', + ' * :py:meth:`~MyService.Client.sample_operation`', + ' .. py:method:: can_paginate(operation_name)', + ' .. py:method:: get_paginator(operation_name)', + ' .. py:method:: get_waiter(waiter_name)', + ' .. py:method:: sample_operation(**kwargs)', + ' **Request Syntax**', + ' ::', + ' response = client.sample_operation(', + ' Foo=\'string\'', + ' Bar=\'string\'', + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns:', + ' **Response Syntax**', + ' ::', + ' {', + ' \'Foo\': \'string\'', + ' \'Bar\': \'string\'', + ' }', + ' **Response Structure**', + ' - *(dict) --*', + ' - **Foo** *(string) --*', + ' - **Bar** *(string) --*', + ] + ) diff --git a/tests/unit/docs/test_collection.py b/tests/unit/docs/test_collection.py index 353b19e..5efb3af 100644 --- a/tests/unit/docs/test_collection.py +++ b/tests/unit/docs/test_collection.py @@ -10,96 +10,109 @@ # 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. -from tests.unit.docs import BaseDocsTest - from boto3.docs.collection import CollectionDocumenter +from tests.unit.docs import BaseDocsTest class TestCollectionDocumenter(BaseDocsTest): def test_document_collections(self): collection_documenter = CollectionDocumenter(self.resource) collection_documenter.document_collections(self.doc_structure) - self.assert_contains_lines_in_order([ - '.. py:attribute:: samples', - ' A collection of Sample resources.' - 'A Sample Collection will include all resources by default, ' - 'and extreme caution should be taken when performing actions ' - 'on all resources.', - ' .. py:method:: all()', - (' Creates an iterable of all Sample resources in the ' - 'collection.'), - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.all()', - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ' .. py:method:: filter(**kwargs)', - (' Creates an iterable of all Sample resources in ' - 'the collection filtered by kwargs passed to method. ' - 'A Sample collection will include all resources by default ' - 'if no filters are provided, and extreme caution should be ' - 'taken when performing actions on all resources'), - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.filter(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ' .. py:method:: limit(**kwargs)', - (' Creates an iterable up to a specified amount of ' - 'Sample resources in the collection.'), - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.limit(', - ' count=123', - ' )', - ' :type count: integer', - (' :param count: The limit to the number of resources ' - 'in the iterable.'), - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ' .. py:method:: operate(**kwargs)', - ' **Request Syntax** ', - ' response = myservice.samples.operate(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar', - ' .. py:method:: page_size(**kwargs)', - (' Creates an iterable of all Sample resources in the ' - 'collection, but limits the number of items returned by ' - 'each service call by the specified amount.'), - ' **Request Syntax** ', - ' ::', - '', - ' sample_iterator = myservice.samples.page_size(', - ' count=123', - ' )', - ' :type count: integer', - (' :param count: The number of items returned by ' - 'each service call'), - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ' ' - ]) + self.assert_contains_lines_in_order( + [ + '.. py:attribute:: samples', + ' A collection of Sample resources.' + 'A Sample Collection will include all resources by default, ' + 'and extreme caution should be taken when performing actions ' + 'on all resources.', + ' .. py:method:: all()', + ( + ' Creates an iterable of all Sample resources in the ' + 'collection.' + ), + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.all()', + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ' .. py:method:: filter(**kwargs)', + ( + ' Creates an iterable of all Sample resources in ' + 'the collection filtered by kwargs passed to method. ' + 'A Sample collection will include all resources by default ' + 'if no filters are provided, and extreme caution should be ' + 'taken when performing actions on all resources' + ), + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.filter(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ' .. py:method:: limit(**kwargs)', + ( + ' Creates an iterable up to a specified amount of ' + 'Sample resources in the collection.' + ), + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.limit(', + ' count=123', + ' )', + ' :type count: integer', + ( + ' :param count: The limit to the number of resources ' + 'in the iterable.' + ), + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ' .. py:method:: operate(**kwargs)', + ' **Request Syntax** ', + ' response = myservice.samples.operate(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ' .. py:method:: page_size(**kwargs)', + ( + ' Creates an iterable of all Sample resources in the ' + 'collection, but limits the number of items returned by ' + 'each service call by the specified amount.' + ), + ' **Request Syntax** ', + ' ::', + '', + ' sample_iterator = myservice.samples.page_size(', + ' count=123', + ' )', + ' :type count: integer', + ( + ' :param count: The number of items returned by ' + 'each service call' + ), + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ' ', + ] + ) diff --git a/tests/unit/docs/test_docstring.py b/tests/unit/docs/test_docstring.py index c149d92..f67d3ff 100644 --- a/tests/unit/docs/test_docstring.py +++ b/tests/unit/docs/test_docstring.py @@ -12,8 +12,8 @@ # language governing permissions and limitations under the License. from botocore.compat import six -from tests.unit.docs import BaseDocsTest from tests import mock +from tests.unit.docs import BaseDocsTest class TestResourceDocstrings(BaseDocsTest): @@ -21,220 +21,269 @@ class TestResourceDocstrings(BaseDocsTest): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.sample_operation) action_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' **Request Syntax**', - ' ::', - ' response = myservice.sample_operation(', - ' Foo=\'string\',', - ' Bar=\'string\'', - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns:', - ' **Response Syntax**', - ' ::', - ' {', - ' \'Foo\': \'string\',', - ' \'Bar\': \'string\'', - ' }', - ' **Response Structure**', - ' - *(dict) --*', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar' - ], action_docstring) + self.assert_contains_lines_in_order( + [ + ' **Request Syntax**', + ' ::', + ' response = myservice.sample_operation(', + ' Foo=\'string\',', + ' Bar=\'string\'', + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns:', + ' **Response Syntax**', + ' ::', + ' {', + ' \'Foo\': \'string\',', + ' \'Bar\': \'string\'', + ' }', + ' **Response Structure**', + ' - *(dict) --*', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ], + action_docstring, + ) def test_load_help(self): sub_resource = self.resource.Sample('Id') with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(sub_resource.load) load_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - (' Calls :py:meth:`MyService.Client.sample_operation` to update ' - 'the attributes of the Sample resource'), - ' **Request Syntax** ', - ' ::', - ' sample.load()', - ' :returns: None', - ], load_docstring) + self.assert_contains_lines_in_order( + [ + ( + ' Calls :py:meth:`MyService.Client.sample_operation` to update ' + 'the attributes of the Sample resource' + ), + ' **Request Syntax** ', + ' ::', + ' sample.load()', + ' :returns: None', + ], + load_docstring, + ) def test_sub_resource_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.Sample) sub_resource_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' Creates a Sample resource.::', - " sample = myservice.Sample('name')", - ' :type name: string', - " :param name: The Sample's name identifier.", - ' :rtype: :py:class:`MyService.Sample`', - ' :returns: A Sample resource', - ], sub_resource_docstring) + self.assert_contains_lines_in_order( + [ + ' Creates a Sample resource.::', + " sample = myservice.Sample('name')", + ' :type name: string', + " :param name: The Sample's name identifier.", + ' :rtype: :py:class:`MyService.Sample`', + ' :returns: A Sample resource', + ], + sub_resource_docstring, + ) def test_attribute_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.Sample('id').__class__.foo) attribute_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' - *(string) --* Documents Foo' - ], attribute_docstring) + self.assert_contains_lines_in_order( + [' - *(string) --* Documents Foo'], attribute_docstring + ) def test_identifier_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.Sample('id').__class__.name) identifier_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - " *(string)* The Sample's name identifier. This " - "**must** be set." - ], identifier_docstring) + self.assert_contains_lines_in_order( + [ + " *(string)* The Sample's name identifier. This " + "**must** be set." + ], + identifier_docstring, + ) def test_reference_help(self): sample_resource = self.resource.Sample('id') with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(sample_resource.__class__.related_sample) reference_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - " (:py:class:`Sample`) The related related_sample " - "if set, otherwise ``None``." - ], reference_docstring) + self.assert_contains_lines_in_order( + [ + " (:py:class:`Sample`) The related related_sample " + "if set, otherwise ``None``." + ], + reference_docstring, + ) def test_collection_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.__class__.samples) collection_method_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' A collection of Sample resources' - ], collection_method_docstring) + self.assert_contains_lines_in_order( + [' A collection of Sample resources'], + collection_method_docstring, + ) def test_collection_all_method_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.samples.all) collection_method_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - (' Creates an iterable of all Sample resources in the ' - 'collection.'), - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.all()', - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ], collection_method_docstring) + self.assert_contains_lines_in_order( + [ + ( + ' Creates an iterable of all Sample resources in the ' + 'collection.' + ), + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.all()', + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ], + collection_method_docstring, + ) def test_collection_filter_method_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.samples.filter) collection_method_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.filter(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ], collection_method_docstring) + self.assert_contains_lines_in_order( + [ + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.filter(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ], + collection_method_docstring, + ) def test_collection_limit_method_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.samples.limit) collection_method_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.limit(', - ' count=123', - ' )', - ' :type count: integer', - (' :param count: The limit to the number of resources ' - 'in the iterable.'), - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ], collection_method_docstring) + self.assert_contains_lines_in_order( + [ + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.limit(', + ' count=123', + ' )', + ' :type count: integer', + ( + ' :param count: The limit to the number of resources ' + 'in the iterable.' + ), + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ], + collection_method_docstring, + ) def test_collection_page_size_method_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.samples.page_size) collection_method_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.page_size(', - ' count=123', - ' )', - ' :type count: integer', - (' :param count: The number of items returned by ' - 'each service call'), - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ], collection_method_docstring) + self.assert_contains_lines_in_order( + [ + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.page_size(', + ' count=123', + ' )', + ' :type count: integer', + ( + ' :param count: The number of items returned by ' + 'each service call' + ), + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ], + collection_method_docstring, + ) def test_collection_chaining_help(self): collection = self.resource.samples.all() with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(collection.all) collection_method_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - (' Creates an iterable of all Sample resources in the ' - 'collection.'), - ' **Request Syntax** ', - ' ::', - ' sample_iterator = myservice.samples.all()', - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resources', - ], collection_method_docstring) + self.assert_contains_lines_in_order( + [ + ( + ' Creates an iterable of all Sample resources in the ' + 'collection.' + ), + ' **Request Syntax** ', + ' ::', + ' sample_iterator = myservice.samples.all()', + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resources', + ], + collection_method_docstring, + ) def test_batch_action_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.samples.operate) batch_action_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - ' **Request Syntax** ', - ' ::', - ' response = myservice.samples.operate(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar', - ], batch_action_docstring) + self.assert_contains_lines_in_order( + [ + ' **Request Syntax** ', + ' ::', + ' response = myservice.samples.operate(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ], + batch_action_docstring, + ) def test_resource_waiter_help(self): with mock.patch('sys.stdout', six.StringIO()) as mock_stdout: help(self.resource.Sample('id').wait_until_complete) resource_waiter_docstring = mock_stdout.getvalue() - self.assert_contains_lines_in_order([ - (' Waits until this Sample is complete. This method calls ' - ':py:meth:`MyService.Waiter.sample_operation_complete.wait` ' - 'which polls. :py:meth:`MyService.Client.sample_operation` every ' - '15 seconds until a successful state is reached. An error ' - 'is returned after 40 failed checks.'), - ' **Request Syntax** ', - ' ::', - ' sample.wait_until_complete(', - " Bar='string'", - ' )', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :returns: None', - ], resource_waiter_docstring) + self.assert_contains_lines_in_order( + [ + ( + ' Waits until this Sample is complete. This method calls ' + ':py:meth:`MyService.Waiter.sample_operation_complete.wait` ' + 'which polls. :py:meth:`MyService.Client.sample_operation` every ' + '15 seconds until a successful state is reached. An error ' + 'is returned after 40 failed checks.' + ), + ' **Request Syntax** ', + ' ::', + ' sample.wait_until_complete(', + " Bar='string'", + ' )', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :returns: None', + ], + resource_waiter_docstring, + ) diff --git a/tests/unit/docs/test_method.py b/tests/unit/docs/test_method.py index db3aa3f..7004bfd 100644 --- a/tests/unit/docs/test_method.py +++ b/tests/unit/docs/test_method.py @@ -10,274 +10,313 @@ # 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. -from botocore.hooks import HierarchicalEmitter from botocore.docs.utils import DocumentedShape +from botocore.hooks import HierarchicalEmitter -from tests.unit.docs import BaseDocsTest -from boto3.resources.model import ResponseResource from boto3.docs.method import document_model_driven_resource_method +from boto3.resources.model import ResponseResource +from tests.unit.docs import BaseDocsTest class TestDocumentModelDrivenResourceMethod(BaseDocsTest): def setUp(self): - super(TestDocumentModelDrivenResourceMethod, self).setUp() + super().setUp() self.event_emitter = HierarchicalEmitter() self.service_model = self.client.meta.service_model self.operation_model = self.service_model.operation_model( - 'SampleOperation') + 'SampleOperation' + ) self.service_resource_model = self.resource.meta.resource_model def test_default(self): document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='response = myservice.foo', - resource_action_model=self.service_resource_model.actions[0] + resource_action_model=self.service_resource_model.actions[0], + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' response = myservice.foo(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' response = myservice.foo(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar' - ]) def test_returns_resource(self): resource_action = self.service_resource_model.actions[0] # Override the return type of the action to be a resource # instead of the regular dictionary. resource_action.resource = ResponseResource( - {'type': 'Sample', - 'identifiers': [{ - 'target': 'Name', 'source': 'requestParameter', - 'path': 'Foo'}]}, - self.resource_json_model) + { + 'type': 'Sample', + 'identifiers': [ + { + 'target': 'Name', + 'source': 'requestParameter', + 'path': 'Foo', + } + ], + }, + self.resource_json_model, + ) document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='sample = myservice.foo', - resource_action_model=resource_action + resource_action_model=resource_action, + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' sample = myservice.foo(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: :py:class:`myservice.Sample`', + ' :returns: Sample resource', + ] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' sample = myservice.foo(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: :py:class:`myservice.Sample`', - ' :returns: Sample resource' - ]) def test_returns_list_of_resource(self): resource_action = self.service_resource_model.actions[1] document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='samples = myservice.foo', - resource_action_model=resource_action + resource_action_model=resource_action, + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' samples = myservice.foo(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: list(:py:class:`myservice.Sample`)', + ' :returns: A list of Sample resource', + ] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' samples = myservice.foo(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: list(:py:class:`myservice.Sample`)', - ' :returns: A list of Sample resource' - ]) def test_include_input(self): include_params = [ DocumentedShape( - name='Biz', type_name='string', documentation='biz docs') + name='Biz', type_name='string', documentation='biz docs' + ) ] document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='response = myservice.foo', include_input=include_params, - resource_action_model=self.service_resource_model.actions[0] + resource_action_model=self.service_resource_model.actions[0], + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' response = myservice.foo(', + " Foo='string',", + " Bar='string',", + " Biz='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :type Biz: string', + ' :param Biz: biz docs', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' response = myservice.foo(', - " Foo='string',", - " Bar='string',", - " Biz='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :type Biz: string', - ' :param Biz: biz docs', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar' - ]) def test_include_output(self): include_params = [ DocumentedShape( - name='Biz', type_name='string', documentation='biz docs') + name='Biz', type_name='string', documentation='biz docs' + ) ] document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='response = myservice.foo', include_output=include_params, - resource_action_model=self.service_resource_model.actions[0] + resource_action_model=self.service_resource_model.actions[0], + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' response = myservice.foo(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string',", + " 'Biz': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ' - **Biz** *(string) --* biz docs', + ] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' response = myservice.foo(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string',", - " 'Biz': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar', - ' - **Biz** *(string) --* biz docs' - ]) def test_exclude_input(self): document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='response = myservice.foo', exclude_input=['Bar'], - resource_action_model=self.service_resource_model.actions[0] + resource_action_model=self.service_resource_model.actions[0], + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' response = myservice.foo(', + " Foo='string',", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string',", + " 'Bar': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ' - **Bar** *(string) --* Documents Bar', + ] + ) + self.assert_not_contains_lines( + [':param Bar: string', 'Bar=\'string\''] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' response = myservice.foo(', - " Foo='string',", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string',", - " 'Bar': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ' - **Bar** *(string) --* Documents Bar' - ]) - self.assert_not_contains_lines([ - ':param Bar: string', - 'Bar=\'string\'' - ]) def test_exclude_output(self): document_model_driven_resource_method( - self.doc_structure, 'foo', self.operation_model, + self.doc_structure, + 'foo', + self.operation_model, event_emitter=self.event_emitter, method_description='This describes the foo method.', example_prefix='response = myservice.foo', exclude_output=['Bar'], - resource_action_model=self.service_resource_model.actions[0] + resource_action_model=self.service_resource_model.actions[0], + ) + self.assert_contains_lines_in_order( + [ + '.. py:method:: foo(**kwargs)', + ' This describes the foo method.', + ' **Request Syntax** ', + ' ::', + ' response = myservice.foo(', + " Foo='string',", + " Bar='string'", + ' )', + ' :type Foo: string', + ' :param Foo: Documents Foo', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :rtype: dict', + ' :returns: ', + ' **Response Syntax** ', + ' ::', + ' {', + " 'Foo': 'string'", + ' }', + ' **Response Structure** ', + ' - *(dict) --* ', + ' - **Foo** *(string) --* Documents Foo', + ] + ) + self.assert_not_contains_lines( + [ + '\'Bar\': \'string\'', + '- **Bar** *(string) --*', + ] ) - self.assert_contains_lines_in_order([ - '.. py:method:: foo(**kwargs)', - ' This describes the foo method.', - ' **Request Syntax** ', - ' ::', - ' response = myservice.foo(', - " Foo='string',", - " Bar='string'", - ' )', - ' :type Foo: string', - ' :param Foo: Documents Foo', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :rtype: dict', - ' :returns: ', - ' **Response Syntax** ', - ' ::', - ' {', - " 'Foo': 'string'", - ' }', - ' **Response Structure** ', - ' - *(dict) --* ', - ' - **Foo** *(string) --* Documents Foo', - ]) - self.assert_not_contains_lines([ - '\'Bar\': \'string\'', - '- **Bar** *(string) --*', - ]) diff --git a/tests/unit/docs/test_resource.py b/tests/unit/docs/test_resource.py index 8d0fe86..2cbb094 100644 --- a/tests/unit/docs/test_resource.py +++ b/tests/unit/docs/test_resource.py @@ -10,88 +10,92 @@ # 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. +from boto3.docs.resource import ResourceDocumenter, ServiceResourceDocumenter from tests.unit.docs import BaseDocsTest -from boto3.docs.resource import ResourceDocumenter -from boto3.docs.resource import ServiceResourceDocumenter - class TestResourceDocumenter(BaseDocsTest): def test_document_resource(self): resource = self.resource.Sample('mysample') resource_documenter = ResourceDocumenter( - resource, self.botocore_session) + resource, self.botocore_session + ) resource_documenter.document_resource(self.doc_structure) - self.assert_contains_lines_in_order([ - '======', - 'Sample', - '======', - '.. py:class:: MyService.Sample(name)', - ' A resource representing an AWS MyService Sample::', - ' import boto3', - " myservice = boto3.resource('myservice')", - " sample = myservice.Sample('name')", - " These are the resource's available identifiers:", - ' * :py:attr:`name`', - " These are the resource's available attributes:", - ' * :py:attr:`bar`', - ' * :py:attr:`foo`', - " These are the resource's available actions:", - ' * :py:meth:`load()`', - ' * :py:meth:`operate()`', - ' * :py:meth:`reload()`', - " These are the resource's available waiters:", - ' * :py:meth:`wait_until_complete()`', - ' .. rst-class:: admonition-title', - ' Identifiers', - ' .. py:attribute:: name', - ' .. rst-class:: admonition-title', - ' Attributes', - ' .. py:attribute:: bar', - ' - *(string) --* Documents Bar', - ' .. py:attribute:: foo', - ' - *(string) --* Documents Foo', - ' .. rst-class:: admonition-title', - ' Actions', - ' .. py:method:: load()', - ' .. py:method:: operate(**kwargs)', - ' .. py:method:: reload()', - ' .. rst-class:: admonition-title', - ' Waiters', - ' .. py:method:: wait_until_complete(**kwargs)', - ]) + self.assert_contains_lines_in_order( + [ + '======', + 'Sample', + '======', + '.. py:class:: MyService.Sample(name)', + ' A resource representing an AWS MyService Sample::', + ' import boto3', + " myservice = boto3.resource('myservice')", + " sample = myservice.Sample('name')", + " These are the resource's available identifiers:", + ' * :py:attr:`name`', + " These are the resource's available attributes:", + ' * :py:attr:`bar`', + ' * :py:attr:`foo`', + " These are the resource's available actions:", + ' * :py:meth:`load()`', + ' * :py:meth:`operate()`', + ' * :py:meth:`reload()`', + " These are the resource's available waiters:", + ' * :py:meth:`wait_until_complete()`', + ' .. rst-class:: admonition-title', + ' Identifiers', + ' .. py:attribute:: name', + ' .. rst-class:: admonition-title', + ' Attributes', + ' .. py:attribute:: bar', + ' - *(string) --* Documents Bar', + ' .. py:attribute:: foo', + ' - *(string) --* Documents Foo', + ' .. rst-class:: admonition-title', + ' Actions', + ' .. py:method:: load()', + ' .. py:method:: operate(**kwargs)', + ' .. py:method:: reload()', + ' .. rst-class:: admonition-title', + ' Waiters', + ' .. py:method:: wait_until_complete(**kwargs)', + ] + ) class TestServiceResourceDocumenter(BaseDocsTest): def test_document_resource(self): resource_documenter = ServiceResourceDocumenter( - self.resource, self.botocore_session) + self.resource, self.botocore_session + ) resource_documenter.document_resource(self.doc_structure) - self.assert_contains_lines_in_order([ - '================', - 'Service Resource', - '================', - '.. py:class:: MyService.ServiceResource()', - ' A resource representing AWS MyService::', - ' import boto3', - " myservice = boto3.resource('myservice')", - " These are the resource's available actions:", - ' * :py:meth:`sample_operation()`', - " These are the resource's available sub-resources:", - ' * :py:meth:`Sample()`', - " These are the resource's available collections:", - ' * :py:attr:`samples`', - ' .. rst-class:: admonition-title', - ' Actions', - ' .. py:method:: sample_operation(**kwargs)', - ' .. rst-class:: admonition-title', - ' Sub-resources', - ' .. py:method:: Sample(name)', - ' .. rst-class:: admonition-title', - ' Collections', - ' .. py:attribute:: samples', - ' .. py:method:: all()', - ' .. py:method:: filter(**kwargs)', - ' .. py:method:: limit(**kwargs)', - ' .. py:method:: page_size(**kwargs)', - ]) + self.assert_contains_lines_in_order( + [ + '================', + 'Service Resource', + '================', + '.. py:class:: MyService.ServiceResource()', + ' A resource representing AWS MyService::', + ' import boto3', + " myservice = boto3.resource('myservice')", + " These are the resource's available actions:", + ' * :py:meth:`sample_operation()`', + " These are the resource's available sub-resources:", + ' * :py:meth:`Sample()`', + " These are the resource's available collections:", + ' * :py:attr:`samples`', + ' .. rst-class:: admonition-title', + ' Actions', + ' .. py:method:: sample_operation(**kwargs)', + ' .. rst-class:: admonition-title', + ' Sub-resources', + ' .. py:method:: Sample(name)', + ' .. rst-class:: admonition-title', + ' Collections', + ' .. py:attribute:: samples', + ' .. py:method:: all()', + ' .. py:method:: filter(**kwargs)', + ' .. py:method:: limit(**kwargs)', + ' .. py:method:: page_size(**kwargs)', + ] + ) diff --git a/tests/unit/docs/test_service.py b/tests/unit/docs/test_service.py index 927c202..7977b37 100644 --- a/tests/unit/docs/test_service.py +++ b/tests/unit/docs/test_service.py @@ -13,9 +13,9 @@ import os import boto3 +from boto3.docs.service import ServiceDocumenter from tests import mock from tests.unit.docs import BaseDocsTest -from boto3.docs.service import ServiceDocumenter class TestServiceDocumenter(BaseDocsTest): @@ -119,8 +119,9 @@ class TestServiceDocumenter(BaseDocsTest): assert 'Waiters' not in contents def test_creates_correct_path_to_examples_based_on_service_name(self): - path = os.sep.join([os.path.dirname(boto3.__file__), - 'examples', 'myservice.rst']) + path = os.sep.join( + [os.path.dirname(boto3.__file__), 'examples', 'myservice.rst'] + ) path = os.path.realpath(path) with mock.patch('os.path.isfile') as isfile: isfile.return_value = False @@ -129,10 +130,10 @@ class TestServiceDocumenter(BaseDocsTest): assert isfile.call_args_list[-1] == mock.call(path) def test_injects_examples_when_found(self): - examples_path = os.sep.join([os.path.dirname(__file__), '..', 'data', - 'examples']) - service_documenter = ServiceDocumenter( - 'myservice', self.session) + examples_path = os.sep.join( + [os.path.dirname(__file__), '..', 'data', 'examples'] + ) + service_documenter = ServiceDocumenter('myservice', self.session) service_documenter.EXAMPLE_PATH = examples_path contents = service_documenter.document_service().decode('utf-8') assert 'This is an example' in contents diff --git a/tests/unit/docs/test_subresource.py b/tests/unit/docs/test_subresource.py index 973a483..e5a1a72 100644 --- a/tests/unit/docs/test_subresource.py +++ b/tests/unit/docs/test_subresource.py @@ -10,21 +10,22 @@ # 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. -from tests.unit.docs import BaseDocsTest - from boto3.docs.subresource import SubResourceDocumenter +from tests.unit.docs import BaseDocsTest class TestSubResourceDocumenter(BaseDocsTest): def test_document_sub_resources(self): sub_resource_documentor = SubResourceDocumenter(self.resource) sub_resource_documentor.document_sub_resources(self.doc_structure) - self.assert_contains_lines_in_order([ - '.. py:method:: Sample(name)', - ' Creates a Sample resource.::', - " sample = myservice.Sample('name')", - ' :type name: string', - " :param name: The Sample's name identifier.", - ' :rtype: :py:class:`MyService.Sample`', - ' :returns: A Sample resource', - ]) + self.assert_contains_lines_in_order( + [ + '.. py:method:: Sample(name)', + ' Creates a Sample resource.::', + " sample = myservice.Sample('name')", + ' :type name: string', + " :param name: The Sample's name identifier.", + ' :rtype: :py:class:`MyService.Sample`', + ' :returns: A Sample resource', + ] + ) diff --git a/tests/unit/docs/test_utils.py b/tests/unit/docs/test_utils.py index 20b14a1..8f70ac3 100644 --- a/tests/unit/docs/test_utils.py +++ b/tests/unit/docs/test_utils.py @@ -10,10 +10,9 @@ # 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. -from tests import unittest - -from boto3.resources.model import Parameter from boto3.docs.utils import get_resource_ignore_params +from boto3.resources.model import Parameter +from tests import unittest class TestGetResourceIgnoreParams(unittest.TestCase): diff --git a/tests/unit/docs/test_waiter.py b/tests/unit/docs/test_waiter.py index d30902a..d1cb57f 100644 --- a/tests/unit/docs/test_waiter.py +++ b/tests/unit/docs/test_waiter.py @@ -10,32 +10,37 @@ # 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. -from tests.unit.docs import BaseDocsTest - from boto3.docs.waiter import WaiterResourceDocumenter +from tests.unit.docs import BaseDocsTest class TestWaiterResourceDocumenter(BaseDocsTest): def test_document_resource_waiters(self): service_waiter_model = self.botocore_session.get_waiter_model( - 'myservice') + 'myservice' + ) subresource = self.resource.Sample('mysample') waiter_documenter = WaiterResourceDocumenter( - subresource, service_waiter_model) + subresource, service_waiter_model + ) waiter_documenter.document_resource_waiters(self.doc_structure) - self.assert_contains_lines_in_order([ - '.. py:method:: wait_until_complete(**kwargs)', - (' Waits until this Sample is complete. This method calls ' - ':py:meth:`MyService.Waiter.sample_operation_complete.wait` ' - 'which polls. :py:meth:`MyService.Client.sample_operation` ' - 'every 15 seconds until a successful state is reached. An ' - 'error is returned after 40 failed checks.'), - ' **Request Syntax** ', - ' ::', - ' sample.wait_until_complete(', - " Bar='string'", - ' )', - ' :type Bar: string', - ' :param Bar: Documents Bar', - ' :returns: None' - ]) + self.assert_contains_lines_in_order( + [ + '.. py:method:: wait_until_complete(**kwargs)', + ( + ' Waits until this Sample is complete. This method calls ' + ':py:meth:`MyService.Waiter.sample_operation_complete.wait` ' + 'which polls. :py:meth:`MyService.Client.sample_operation` ' + 'every 15 seconds until a successful state is reached. An ' + 'error is returned after 40 failed checks.' + ), + ' **Request Syntax** ', + ' ::', + ' sample.wait_until_complete(', + " Bar='string'", + ' )', + ' :type Bar: string', + ' :param Bar: Documents Bar', + ' :returns: None', + ] + ) diff --git a/tests/unit/dynamodb/test_conditions.py b/tests/unit/dynamodb/test_conditions.py index 4ce4b8a..9b2be8d 100644 --- a/tests/unit/dynamodb/test_conditions.py +++ b/tests/unit/dynamodb/test_conditions.py @@ -14,18 +14,34 @@ import copy import pytest -from tests import unittest - -from boto3.exceptions import ( - DynamoDBOperationNotSupportedError, DynamoDBNeedsConditionError, - DynamoDBNeedsKeyConditionError, -) from boto3.dynamodb.conditions import ( - Attr, Key, And, Or, Not, Equals, LessThan, - LessThanEquals, GreaterThan, GreaterThanEquals, BeginsWith, Between, - NotEquals, In, AttributeExists, AttributeNotExists, Contains, Size, - AttributeType, ConditionExpressionBuilder + And, + Attr, + AttributeExists, + AttributeNotExists, + AttributeType, + BeginsWith, + Between, + ConditionExpressionBuilder, + Contains, + Equals, + GreaterThan, + GreaterThanEquals, + In, + Key, + LessThan, + LessThanEquals, + Not, + NotEquals, + Or, + Size, ) +from boto3.exceptions import ( + DynamoDBNeedsConditionError, + DynamoDBNeedsKeyConditionError, + DynamoDBOperationNotSupportedError, +) +from tests import unittest class TestK(unittest.TestCase): @@ -55,22 +71,26 @@ class TestK(unittest.TestCase): def test_lte(self): assert self.attr.lte(self.value) == LessThanEquals( - self.attr, self.value) + self.attr, self.value + ) def test_gt(self): assert self.attr.gt(self.value) == GreaterThan(self.attr, self.value) def test_gte(self): assert self.attr.gte(self.value) == GreaterThanEquals( - self.attr, self.value) + self.attr, self.value + ) def test_begins_with(self): assert self.attr.begins_with(self.value) == BeginsWith( - self.attr, self.value) + self.attr, self.value + ) def test_between(self): assert self.attr.between(self.value, self.value2) == Between( - self.attr, self.value, self.value2) + self.attr, self.value, self.value2 + ) def test_attribute_equality(self): attr_copy = copy.deepcopy(self.attr) @@ -144,14 +164,17 @@ class TestA(TestK): assert self.attr.not_exists() == AttributeNotExists(self.attr) def test_contains(self): - assert self.attr.contains(self.value) == Contains(self.attr, self.value) + assert self.attr.contains(self.value) == Contains( + self.attr, self.value + ) def test_size(self): assert self.attr.size() == Size(self.attr) def test_attribute_type(self): assert self.attr.attribute_type(self.value) == AttributeType( - self.attr, self.value) + self.attr, self.value + ) def test_ne_equality(self): attr_copy = copy.deepcopy(self.attr) @@ -201,8 +224,9 @@ class TestConditions(unittest.TestCase): self.value = Attr('mykey') self.value2 = 'foo' - def build_and_assert_expression(self, condition, - reference_expression_dict): + def build_and_assert_expression( + self, condition, reference_expression_dict + ): expression_dict = condition.get_expression() assert expression_dict == reference_expression_dict @@ -253,105 +277,167 @@ class TestConditions(unittest.TestCase): def test_eq(self): self.build_and_assert_expression( Equals(self.value, self.value2), - {'format': '{0} {operator} {1}', - 'operator': '=', 'values': (self.value, self.value2)}) + { + 'format': '{0} {operator} {1}', + 'operator': '=', + 'values': (self.value, self.value2), + }, + ) def test_ne(self): self.build_and_assert_expression( NotEquals(self.value, self.value2), - {'format': '{0} {operator} {1}', - 'operator': '<>', 'values': (self.value, self.value2)}) + { + 'format': '{0} {operator} {1}', + 'operator': '<>', + 'values': (self.value, self.value2), + }, + ) def test_lt(self): self.build_and_assert_expression( LessThan(self.value, self.value2), - {'format': '{0} {operator} {1}', - 'operator': '<', 'values': (self.value, self.value2)}) + { + 'format': '{0} {operator} {1}', + 'operator': '<', + 'values': (self.value, self.value2), + }, + ) def test_lte(self): self.build_and_assert_expression( LessThanEquals(self.value, self.value2), - {'format': '{0} {operator} {1}', - 'operator': '<=', 'values': (self.value, self.value2)}) + { + 'format': '{0} {operator} {1}', + 'operator': '<=', + 'values': (self.value, self.value2), + }, + ) def test_gt(self): self.build_and_assert_expression( GreaterThan(self.value, self.value2), - {'format': '{0} {operator} {1}', - 'operator': '>', 'values': (self.value, self.value2)}) + { + 'format': '{0} {operator} {1}', + 'operator': '>', + 'values': (self.value, self.value2), + }, + ) def test_gte(self): self.build_and_assert_expression( GreaterThanEquals(self.value, self.value2), - {'format': '{0} {operator} {1}', - 'operator': '>=', 'values': (self.value, self.value2)}) + { + 'format': '{0} {operator} {1}', + 'operator': '>=', + 'values': (self.value, self.value2), + }, + ) def test_in(self): cond = In(self.value, (self.value2)) self.build_and_assert_expression( cond, - {'format': '{0} {operator} {1}', - 'operator': 'IN', 'values': (self.value, (self.value2))}) + { + 'format': '{0} {operator} {1}', + 'operator': 'IN', + 'values': (self.value, (self.value2)), + }, + ) assert cond.has_grouped_values def test_bet(self): self.build_and_assert_expression( Between(self.value, self.value2, 'foo2'), - {'format': '{0} {operator} {1} AND {2}', - 'operator': 'BETWEEN', - 'values': (self.value, self.value2, 'foo2')}) + { + 'format': '{0} {operator} {1} AND {2}', + 'operator': 'BETWEEN', + 'values': (self.value, self.value2, 'foo2'), + }, + ) def test_beg(self): self.build_and_assert_expression( BeginsWith(self.value, self.value2), - {'format': '{operator}({0}, {1})', - 'operator': 'begins_with', 'values': (self.value, self.value2)}) + { + 'format': '{operator}({0}, {1})', + 'operator': 'begins_with', + 'values': (self.value, self.value2), + }, + ) def test_cont(self): self.build_and_assert_expression( Contains(self.value, self.value2), - {'format': '{operator}({0}, {1})', - 'operator': 'contains', 'values': (self.value, self.value2)}) + { + 'format': '{operator}({0}, {1})', + 'operator': 'contains', + 'values': (self.value, self.value2), + }, + ) def test_ae(self): self.build_and_assert_expression( AttributeExists(self.value), - {'format': '{operator}({0})', - 'operator': 'attribute_exists', 'values': (self.value,)}) + { + 'format': '{operator}({0})', + 'operator': 'attribute_exists', + 'values': (self.value,), + }, + ) def test_ane(self): self.build_and_assert_expression( AttributeNotExists(self.value), - {'format': '{operator}({0})', - 'operator': 'attribute_not_exists', 'values': (self.value,)}) + { + 'format': '{operator}({0})', + 'operator': 'attribute_not_exists', + 'values': (self.value,), + }, + ) def test_size(self): self.build_and_assert_expression( Size(self.value), - {'format': '{operator}({0})', - 'operator': 'size', 'values': (self.value,)}) + { + 'format': '{operator}({0})', + 'operator': 'size', + 'values': (self.value,), + }, + ) def test_size_can_use_attr_methods(self): size = Size(self.value) self.build_and_assert_expression( size.eq(self.value), - {'format': '{0} {operator} {1}', - 'operator': '=', 'values': (size, self.value)}) + { + 'format': '{0} {operator} {1}', + 'operator': '=', + 'values': (size, self.value), + }, + ) def test_size_can_use_and(self): size = Size(self.value) ae = AttributeExists(self.value) self.build_and_assert_expression( size & ae, - {'format': '({0} {operator} {1})', - 'operator': 'AND', 'values': (size, ae)}) + { + 'format': '({0} {operator} {1})', + 'operator': 'AND', + 'values': (size, ae), + }, + ) def test_attribute_type(self): self.build_and_assert_expression( AttributeType(self.value, self.value2), - {'format': '{operator}({0}, {1})', - 'operator': 'attribute_type', - 'values': (self.value, self.value2)}) + { + 'format': '{operator}({0}, {1})', + 'operator': 'attribute_type', + 'values': (self.value, self.value2), + }, + ) def test_and(self): cond1 = Equals(self.value, self.value2) @@ -359,8 +445,12 @@ class TestConditions(unittest.TestCase): and_cond = And(cond1, cond2) self.build_and_assert_expression( and_cond, - {'format': '({0} {operator} {1})', - 'operator': 'AND', 'values': (cond1, cond2)}) + { + 'format': '({0} {operator} {1})', + 'operator': 'AND', + 'values': (cond1, cond2), + }, + ) def test_or(self): cond1 = Equals(self.value, self.value2) @@ -368,16 +458,24 @@ class TestConditions(unittest.TestCase): or_cond = Or(cond1, cond2) self.build_and_assert_expression( or_cond, - {'format': '({0} {operator} {1})', - 'operator': 'OR', 'values': (cond1, cond2)}) + { + 'format': '({0} {operator} {1})', + 'operator': 'OR', + 'values': (cond1, cond2), + }, + ) def test_not(self): cond = Equals(self.value, self.value2) not_cond = Not(cond) self.build_and_assert_expression( not_cond, - {'format': '({operator} {0})', - 'operator': 'NOT', 'values': (cond,)}) + { + 'format': '({operator} {0})', + 'operator': 'NOT', + 'values': (cond,), + }, + ) class TestConditionExpressionBuilder(unittest.TestCase): @@ -385,10 +483,16 @@ class TestConditionExpressionBuilder(unittest.TestCase): self.builder = ConditionExpressionBuilder() def assert_condition_expression_build( - self, condition, ref_string, ref_names, ref_values, - is_key_condition=False): + self, + condition, + ref_string, + ref_names, + ref_values, + is_key_condition=False, + ): exp_string, names, values = self.builder.build_expression( - condition, is_key_condition=is_key_condition) + condition, is_key_condition=is_key_condition + ) assert exp_string == ref_string assert names == ref_names assert values == ref_values @@ -401,127 +505,168 @@ class TestConditionExpressionBuilder(unittest.TestCase): def test_build_expression_eq(self): a = Attr('myattr') self.assert_condition_expression_build( - a.eq('foo'), '#n0 = :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.eq('foo'), '#n0 = :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_reset(self): a = Attr('myattr') self.assert_condition_expression_build( - a.eq('foo'), '#n0 = :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.eq('foo'), '#n0 = :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) self.assert_condition_expression_build( - a.eq('foo'), '#n1 = :v1', {'#n1': 'myattr'}, {':v1': 'foo'}) + a.eq('foo'), '#n1 = :v1', {'#n1': 'myattr'}, {':v1': 'foo'} + ) self.builder.reset() self.assert_condition_expression_build( - a.eq('foo'), '#n0 = :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.eq('foo'), '#n0 = :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_expression_lt(self): a = Attr('myattr') self.assert_condition_expression_build( - a.lt('foo'), '#n0 < :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.lt('foo'), '#n0 < :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_expression_lte(self): a1 = Attr('myattr') self.assert_condition_expression_build( - a1.lte('foo'), '#n0 <= :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a1.lte('foo'), '#n0 <= :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_expression_gt(self): a = Attr('myattr') self.assert_condition_expression_build( - a.gt('foo'), '#n0 > :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.gt('foo'), '#n0 > :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_expression_gte(self): a = Attr('myattr') self.assert_condition_expression_build( - a.gte('foo'), '#n0 >= :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.gte('foo'), '#n0 >= :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_expression_begins_with(self): a = Attr('myattr') self.assert_condition_expression_build( - a.begins_with('foo'), 'begins_with(#n0, :v0)', - {'#n0': 'myattr'}, {':v0': 'foo'}) + a.begins_with('foo'), + 'begins_with(#n0, :v0)', + {'#n0': 'myattr'}, + {':v0': 'foo'}, + ) def test_build_expression_between(self): a = Attr('myattr') self.assert_condition_expression_build( - a.between('foo', 'foo2'), '#n0 BETWEEN :v0 AND :v1', - {'#n0': 'myattr'}, {':v0': 'foo', ':v1': 'foo2'}) + a.between('foo', 'foo2'), + '#n0 BETWEEN :v0 AND :v1', + {'#n0': 'myattr'}, + {':v0': 'foo', ':v1': 'foo2'}, + ) def test_build_expression_ne(self): a = Attr('myattr') self.assert_condition_expression_build( - a.ne('foo'), '#n0 <> :v0', {'#n0': 'myattr'}, {':v0': 'foo'}) + a.ne('foo'), '#n0 <> :v0', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_expression_in(self): a = Attr('myattr') self.assert_condition_expression_build( - a.is_in([1, 2, 3]), '#n0 IN (:v0, :v1, :v2)', - {'#n0': 'myattr'}, {':v0': 1, ':v1': 2, ':v2': 3}) + a.is_in([1, 2, 3]), + '#n0 IN (:v0, :v1, :v2)', + {'#n0': 'myattr'}, + {':v0': 1, ':v1': 2, ':v2': 3}, + ) def test_build_expression_exists(self): a = Attr('myattr') self.assert_condition_expression_build( - a.exists(), 'attribute_exists(#n0)', {'#n0': 'myattr'}, {}) + a.exists(), 'attribute_exists(#n0)', {'#n0': 'myattr'}, {} + ) def test_build_expression_not_exists(self): a = Attr('myattr') self.assert_condition_expression_build( - a.not_exists(), 'attribute_not_exists(#n0)', {'#n0': 'myattr'}, {}) + a.not_exists(), 'attribute_not_exists(#n0)', {'#n0': 'myattr'}, {} + ) def test_build_contains(self): a = Attr('myattr') self.assert_condition_expression_build( - a.contains('foo'), 'contains(#n0, :v0)', - {'#n0': 'myattr'}, {':v0': 'foo'}) + a.contains('foo'), + 'contains(#n0, :v0)', + {'#n0': 'myattr'}, + {':v0': 'foo'}, + ) def test_build_size(self): a = Attr('myattr') self.assert_condition_expression_build( - a.size(), 'size(#n0)', {'#n0': 'myattr'}, {}) + a.size(), 'size(#n0)', {'#n0': 'myattr'}, {} + ) def test_build_size_with_other_conditons(self): a = Attr('myattr') self.assert_condition_expression_build( - a.size().eq(5), 'size(#n0) = :v0', {'#n0': 'myattr'}, {':v0': 5}) + a.size().eq(5), 'size(#n0) = :v0', {'#n0': 'myattr'}, {':v0': 5} + ) def test_build_attribute_type(self): a = Attr('myattr') self.assert_condition_expression_build( - a.attribute_type('foo'), 'attribute_type(#n0, :v0)', - {'#n0': 'myattr'}, {':v0': 'foo'}) + a.attribute_type('foo'), + 'attribute_type(#n0, :v0)', + {'#n0': 'myattr'}, + {':v0': 'foo'}, + ) def test_build_and(self): a = Attr('myattr') a2 = Attr('myattr2') self.assert_condition_expression_build( - a.eq('foo') & a2.eq('bar'), '(#n0 = :v0 AND #n1 = :v1)', - {'#n0': 'myattr', '#n1': 'myattr2'}, {':v0': 'foo', ':v1': 'bar'}) + a.eq('foo') & a2.eq('bar'), + '(#n0 = :v0 AND #n1 = :v1)', + {'#n0': 'myattr', '#n1': 'myattr2'}, + {':v0': 'foo', ':v1': 'bar'}, + ) def test_build_or(self): a = Attr('myattr') a2 = Attr('myattr2') self.assert_condition_expression_build( - a.eq('foo') | a2.eq('bar'), '(#n0 = :v0 OR #n1 = :v1)', - {'#n0': 'myattr', '#n1': 'myattr2'}, {':v0': 'foo', ':v1': 'bar'}) + a.eq('foo') | a2.eq('bar'), + '(#n0 = :v0 OR #n1 = :v1)', + {'#n0': 'myattr', '#n1': 'myattr2'}, + {':v0': 'foo', ':v1': 'bar'}, + ) def test_build_not(self): a = Attr('myattr') self.assert_condition_expression_build( - ~a.eq('foo'), '(NOT #n0 = :v0)', - {'#n0': 'myattr'}, {':v0': 'foo'}) + ~a.eq('foo'), '(NOT #n0 = :v0)', {'#n0': 'myattr'}, {':v0': 'foo'} + ) def test_build_attribute_with_attr_value(self): a = Attr('myattr') value = Attr('myreference') self.assert_condition_expression_build( - a.eq(value), '#n0 = #n1', - {'#n0': 'myattr', '#n1': 'myreference'}, {}) + a.eq(value), + '#n0 = #n1', + {'#n0': 'myattr', '#n1': 'myreference'}, + {}, + ) def test_build_with_is_key_condition(self): k = Key('myattr') self.assert_condition_expression_build( - k.eq('foo'), '#n0 = :v0', - {'#n0': 'myattr'}, {':v0': 'foo'}, is_key_condition=True) + k.eq('foo'), + '#n0 = :v0', + {'#n0': 'myattr'}, + {':v0': 'foo'}, + is_key_condition=True, + ) def test_build_with_is_key_condition_throws_error(self): a = Attr('myattr') @@ -531,20 +676,26 @@ class TestConditionExpressionBuilder(unittest.TestCase): def test_build_attr_map(self): a = Attr('MyMap.MyKey') self.assert_condition_expression_build( - a.eq('foo'), '#n0.#n1 = :v0', {'#n0': 'MyMap', '#n1': 'MyKey'}, - {':v0': 'foo'}) + a.eq('foo'), + '#n0.#n1 = :v0', + {'#n0': 'MyMap', '#n1': 'MyKey'}, + {':v0': 'foo'}, + ) def test_build_attr_list(self): a = Attr('MyList[0]') self.assert_condition_expression_build( - a.eq('foo'), '#n0[0] = :v0', {'#n0': 'MyList'}, {':v0': 'foo'}) + a.eq('foo'), '#n0[0] = :v0', {'#n0': 'MyList'}, {':v0': 'foo'} + ) def test_build_nested_attr_map_list(self): a = Attr('MyMap.MyList[2].MyElement') self.assert_condition_expression_build( - a.eq('foo'), '#n0.#n1[2].#n2 = :v0', + a.eq('foo'), + '#n0.#n1[2].#n2 = :v0', {'#n0': 'MyMap', '#n1': 'MyList', '#n2': 'MyElement'}, - {':v0': 'foo'}) + {':v0': 'foo'}, + ) def test_build_double_nested_and_or(self): a = Attr('myattr') @@ -552,6 +703,11 @@ class TestConditionExpressionBuilder(unittest.TestCase): self.assert_condition_expression_build( (a.eq('foo') & a2.eq('foo2')) | (a.eq('bar') & a2.eq('bar2')), '((#n0 = :v0 AND #n1 = :v1) OR (#n2 = :v2 AND #n3 = :v3))', - {'#n0': 'myattr', '#n1': 'myattr2', '#n2': 'myattr', - '#n3': 'myattr2'}, - {':v0': 'foo', ':v1': 'foo2', ':v2': 'bar', ':v3': 'bar2'}) + { + '#n0': 'myattr', + '#n1': 'myattr2', + '#n2': 'myattr', + '#n3': 'myattr2', + }, + {':v0': 'foo', ':v1': 'foo2', ':v2': 'bar', ':v3': 'bar2'}, + ) diff --git a/tests/unit/dynamodb/test_table.py b/tests/unit/dynamodb/test_table.py index d7386a8..56f8dfc 100644 --- a/tests/unit/dynamodb/test_table.py +++ b/tests/unit/dynamodb/test_table.py @@ -10,9 +10,8 @@ # 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. -from tests import unittest, mock - from boto3.dynamodb.table import BatchWriter +from tests import mock, unittest class BaseTransformationTest(unittest.TestCase): @@ -24,14 +23,16 @@ class BaseTransformationTest(unittest.TestCase): self.client.batch_write_item.return_value = {'UnprocessedItems': {}} self.table_name = 'tablename' self.flush_amount = 2 - self.batch_writer = BatchWriter(self.table_name, self.client, - self.flush_amount) + self.batch_writer = BatchWriter( + self.table_name, self.client, self.flush_amount + ) def assert_batch_write_calls_are(self, expected_batch_writes): - assert self.client.batch_write_item.call_count == len(expected_batch_writes) + assert self.client.batch_write_item.call_count == len( + expected_batch_writes + ) batch_write_calls = [ - args[1] for args in - self.client.batch_write_item.call_args_list + args[1] for args in self.client.batch_write_item.call_args_list ] assert batch_write_calls == expected_batch_writes @@ -123,7 +124,7 @@ class BaseTransformationTest(unittest.TestCase): }, }, # Then the last response shows that everything went through - {'UnprocessedItems': {}} + {'UnprocessedItems': {}}, ] self.batch_writer.put_item(Item={'Hash': 'foo1'}) self.batch_writer.put_item(Item={'Hash': 'foo2'}) @@ -155,15 +156,17 @@ class BaseTransformationTest(unittest.TestCase): def test_all_items_flushed_on_exit(self): with self.batch_writer as b: b.put_item(Item={'Hash': 'foo1'}) - self.assert_batch_write_calls_are([ - { - 'RequestItems': { - self.table_name: [ - {'PutRequest': {'Item': {'Hash': 'foo1'}}}, - ] + self.assert_batch_write_calls_are( + [ + { + 'RequestItems': { + self.table_name: [ + {'PutRequest': {'Item': {'Hash': 'foo1'}}}, + ] + }, }, - }, - ]) + ] + ) def test_never_send_more_than_max_batch_size(self): # Suppose the server sends backs a response that indicates that @@ -184,9 +187,7 @@ class BaseTransformationTest(unittest.TestCase): ], }, }, - { - 'UnprocessedItems': {} - }, + {'UnprocessedItems': {}}, ] with BatchWriter(self.table_name, self.client, flush_amount=2) as b: b.put_item(Item={'Hash': 'foo1'}) @@ -222,8 +223,9 @@ class BaseTransformationTest(unittest.TestCase): ] } } - self.assert_batch_write_calls_are([first_batch, second_batch, - third_batch]) + self.assert_batch_write_calls_are( + [first_batch, second_batch, third_batch] + ) def test_repeated_flushing_on_exit(self): # We're going to simulate unprocessed_items @@ -244,9 +246,7 @@ class BaseTransformationTest(unittest.TestCase): ], }, }, - { - 'UnprocessedItems': {} - }, + {'UnprocessedItems': {}}, ] with BatchWriter(self.table_name, self.client, flush_amount=4) as b: b.put_item(Item={'Hash': 'foo1'}) @@ -281,68 +281,71 @@ class BaseTransformationTest(unittest.TestCase): ] } } - self.assert_batch_write_calls_are([first_batch, second_batch, - third_batch]) + self.assert_batch_write_calls_are( + [first_batch, second_batch, third_batch] + ) def test_auto_dedup_for_dup_requests(self): - with BatchWriter(self.table_name, self.client, - flush_amount=5, overwrite_by_pkeys=["pkey", "skey"]) as b: + with BatchWriter( + self.table_name, + self.client, + flush_amount=5, + overwrite_by_pkeys=["pkey", "skey"], + ) as b: # dup 1 - b.put_item(Item={ - 'pkey': 'foo1', - 'skey': 'bar1', - 'other': 'other1' - }) - b.put_item(Item={ - 'pkey': 'foo1', - 'skey': 'bar1', - 'other': 'other2' - }) + b.put_item( + Item={'pkey': 'foo1', 'skey': 'bar1', 'other': 'other1'} + ) + b.put_item( + Item={'pkey': 'foo1', 'skey': 'bar1', 'other': 'other2'} + ) # dup 2 - b.delete_item(Key={ - 'pkey': 'foo1', - 'skey': 'bar2', - }) - b.put_item(Item={ - 'pkey': 'foo1', - 'skey': 'bar2', - 'other': 'other3' - }) + b.delete_item( + Key={ + 'pkey': 'foo1', + 'skey': 'bar2', + } + ) + b.put_item( + Item={'pkey': 'foo1', 'skey': 'bar2', 'other': 'other3'} + ) # dup 3 - b.put_item(Item={ - 'pkey': 'foo2', - 'skey': 'bar2', - 'other': 'other3' - }) - b.delete_item(Key={ - 'pkey': 'foo2', - 'skey': 'bar2', - }) + b.put_item( + Item={'pkey': 'foo2', 'skey': 'bar2', 'other': 'other3'} + ) + b.delete_item( + Key={ + 'pkey': 'foo2', + 'skey': 'bar2', + } + ) # dup 4 - b.delete_item(Key={ - 'pkey': 'foo2', - 'skey': 'bar3', - }) - b.delete_item(Key={ - 'pkey': 'foo2', - 'skey': 'bar3', - }) + b.delete_item( + Key={ + 'pkey': 'foo2', + 'skey': 'bar3', + } + ) + b.delete_item( + Key={ + 'pkey': 'foo2', + 'skey': 'bar3', + } + ) # 5 - b.delete_item(Key={ - 'pkey': 'foo3', - 'skey': 'bar3', - }) + b.delete_item( + Key={ + 'pkey': 'foo3', + 'skey': 'bar3', + } + ) # 2nd batch - b.put_item(Item={ - 'pkey': 'foo1', - 'skey': 'bar1', - 'other': 'other1' - }) - b.put_item(Item={ - 'pkey': 'foo1', - 'skey': 'bar1', - 'other': 'other2' - }) + b.put_item( + Item={'pkey': 'foo1', 'skey': 'bar1', 'other': 'other1'} + ) + b.put_item( + Item={'pkey': 'foo1', 'skey': 'bar1', 'other': 'other2'} + ) first_batch = { 'RequestItems': { @@ -352,7 +355,7 @@ class BaseTransformationTest(unittest.TestCase): 'Item': { 'pkey': 'foo1', 'skey': 'bar1', - 'other': 'other2' + 'other': 'other2', } } }, @@ -361,7 +364,7 @@ class BaseTransformationTest(unittest.TestCase): 'Item': { 'pkey': 'foo1', 'skey': 'bar2', - 'other': 'other3' + 'other': 'other3', } } }, @@ -400,7 +403,7 @@ class BaseTransformationTest(unittest.TestCase): 'Item': { 'pkey': 'foo1', 'skey': 'bar1', - 'other': 'other2' + 'other': 'other2', } } }, diff --git a/tests/unit/dynamodb/test_transform.py b/tests/unit/dynamodb/test_transform.py index 8dec344..0144186 100644 --- a/tests/unit/dynamodb/test_transform.py +++ b/tests/unit/dynamodb/test_transform.py @@ -10,17 +10,18 @@ # 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. -from tests import unittest, mock +from botocore.model import OperationModel, ServiceModel -from botocore.model import ServiceModel, OperationModel - -from boto3.resources.base import ResourceMeta, ServiceResource -from boto3.dynamodb.transform import ParameterTransformer -from boto3.dynamodb.transform import TransformationInjector -from boto3.dynamodb.transform import DynamoDBHighLevelResource -from boto3.dynamodb.transform import register_high_level_interface -from boto3.dynamodb.transform import copy_dynamodb_params from boto3.dynamodb.conditions import Attr, Key +from boto3.dynamodb.transform import ( + DynamoDBHighLevelResource, + ParameterTransformer, + TransformationInjector, + copy_dynamodb_params, + register_high_level_interface, +) +from boto3.resources.base import ResourceMeta, ServiceResource +from tests import mock, unittest class BaseTransformationTest(unittest.TestCase): @@ -40,25 +41,23 @@ class BaseTransformationTest(unittest.TestCase): 'SampleOperation': { 'name': 'SampleOperation', 'input': {'shape': 'SampleOperationInputOutput'}, - 'output': {'shape': 'SampleOperationInputOutput'} + 'output': {'shape': 'SampleOperationInputOutput'}, } }, 'shapes': { 'SampleOperationInputOutput': { 'type': 'structure', - 'members': {} + 'members': {}, }, - 'String': { - 'type': 'string' - } - } + 'String': {'type': 'string'}, + }, } def build_models(self): self.service_model = ServiceModel(self.json_model) self.operation_model = OperationModel( self.json_model['operations']['SampleOperation'], - self.service_model + self.service_model, ) def add_input_shape(self, shape): @@ -74,7 +73,7 @@ class BaseTransformationTest(unittest.TestCase): class TestInputOutputTransformer(BaseTransformationTest): def setUp(self): - super(TestInputOutputTransformer, self).setUp() + super().setUp() self.transformation = lambda params: self.transformed_value self.add_shape({self.target_shape: {'type': 'string'}}) @@ -90,32 +89,36 @@ class TestInputOutputTransformer(BaseTransformationTest): 'type': 'structure', 'members': { 'TransformMe': {'shape': self.target_shape}, - 'LeaveAlone': {'shape': 'String'} - } + 'LeaveAlone': {'shape': 'String'}, + }, } } self.add_input_shape(input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, + params=input_params, + model=self.operation_model.input_shape, transformation=self.transformation, - target_shape=self.target_shape) + target_shape=self.target_shape, + ) assert input_params == { 'Structure': { 'TransformMe': self.transformed_value, - 'LeaveAlone': self.original_value}} + 'LeaveAlone': self.original_value, + } + } def test_transform_map(self): input_params = { 'TransformMe': {'foo': self.original_value}, - 'LeaveAlone': {'foo': self.original_value} + 'LeaveAlone': {'foo': self.original_value}, } targeted_input_shape = { 'TransformMe': { 'type': 'map', 'key': {'shape': 'String'}, - 'value': {'shape': self.target_shape} + 'value': {'shape': self.target_shape}, } } @@ -123,7 +126,7 @@ class TestInputOutputTransformer(BaseTransformationTest): 'LeaveAlone': { 'type': 'map', 'key': {'shape': 'String'}, - 'value': {'shape': 'String'} + 'value': {'shape': 'String'}, } } @@ -131,54 +134,53 @@ class TestInputOutputTransformer(BaseTransformationTest): self.add_input_shape(untargeted_input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, + params=input_params, + model=self.operation_model.input_shape, transformation=self.transformation, - target_shape=self.target_shape) + target_shape=self.target_shape, + ) assert input_params == { 'TransformMe': {'foo': self.transformed_value}, - 'LeaveAlone': {'foo': self.original_value} + 'LeaveAlone': {'foo': self.original_value}, } def test_transform_list(self): input_params = { - 'TransformMe': [ - self.original_value, self.original_value - ], - 'LeaveAlone': [ - self.original_value, self.original_value - ] + 'TransformMe': [self.original_value, self.original_value], + 'LeaveAlone': [self.original_value, self.original_value], } targeted_input_shape = { 'TransformMe': { 'type': 'list', - 'member': {'shape': self.target_shape} + 'member': {'shape': self.target_shape}, } } untargeted_input_shape = { - 'LeaveAlone': { - 'type': 'list', - 'member': {'shape': 'String'} - } + 'LeaveAlone': {'type': 'list', 'member': {'shape': 'String'}} } self.add_input_shape(targeted_input_shape) self.add_input_shape(untargeted_input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, - transformation=self.transformation, target_shape=self.target_shape) + params=input_params, + model=self.operation_model.input_shape, + transformation=self.transformation, + target_shape=self.target_shape, + ) assert input_params == { 'TransformMe': [self.transformed_value, self.transformed_value], - 'LeaveAlone': [self.original_value, self.original_value]} + 'LeaveAlone': [self.original_value, self.original_value], + } def test_transform_nested_structure(self): input_params = { 'WrapperStructure': { 'Structure': { 'TransformMe': self.original_value, - 'LeaveAlone': self.original_value + 'LeaveAlone': self.original_value, } } } @@ -188,48 +190,46 @@ class TestInputOutputTransformer(BaseTransformationTest): 'type': 'structure', 'members': { 'TransformMe': {'shape': self.target_shape}, - 'LeaveAlone': {'shape': 'String'} - } + 'LeaveAlone': {'shape': 'String'}, + }, } } input_shape = { 'WrapperStructure': { 'type': 'structure', - 'members': {'Structure': {'shape': 'Structure'}}} + 'members': {'Structure': {'shape': 'Structure'}}, + } } self.add_shape(structure_shape) self.add_input_shape(input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, + params=input_params, + model=self.operation_model.input_shape, transformation=self.transformation, - target_shape=self.target_shape) + target_shape=self.target_shape, + ) assert input_params == { 'WrapperStructure': { - 'Structure': {'TransformMe': self.transformed_value, - 'LeaveAlone': self.original_value}}} + 'Structure': { + 'TransformMe': self.transformed_value, + 'LeaveAlone': self.original_value, + } + } + } def test_transform_nested_map(self): input_params = { - 'TargetedWrapperMap': { - 'foo': { - 'bar': self.original_value - } - }, - 'UntargetedWrapperMap': { - 'foo': { - 'bar': self.original_value - } - } - + 'TargetedWrapperMap': {'foo': {'bar': self.original_value}}, + 'UntargetedWrapperMap': {'foo': {'bar': self.original_value}}, } targeted_map_shape = { 'TransformMeMap': { 'type': 'map', 'key': {'shape': 'String'}, - 'value': {'shape': self.target_shape} + 'value': {'shape': self.target_shape}, } } @@ -237,7 +237,8 @@ class TestInputOutputTransformer(BaseTransformationTest): 'TargetedWrapperMap': { 'type': 'map', 'key': {'shape': 'Name'}, - 'value': {'shape': 'TransformMeMap'}} + 'value': {'shape': 'TransformMeMap'}, + } } self.add_shape(targeted_map_shape) @@ -247,7 +248,7 @@ class TestInputOutputTransformer(BaseTransformationTest): 'LeaveAloneMap': { 'type': 'map', 'key': {'shape': 'String'}, - 'value': {'shape': 'String'} + 'value': {'shape': 'String'}, } } @@ -255,18 +256,23 @@ class TestInputOutputTransformer(BaseTransformationTest): 'UntargetedWrapperMap': { 'type': 'map', 'key': {'shape': 'Name'}, - 'value': {'shape': 'LeaveAloneMap'}} + 'value': {'shape': 'LeaveAloneMap'}, + } } self.add_shape(untargeted_map_shape) self.add_input_shape(untargeted_wrapper_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, - transformation=self.transformation, target_shape=self.target_shape) + params=input_params, + model=self.operation_model.input_shape, + transformation=self.transformation, + target_shape=self.target_shape, + ) assert input_params == { 'TargetedWrapperMap': {'foo': {'bar': self.transformed_value}}, - 'UntargetedWrapperMap': {'foo': {'bar': self.original_value}}} + 'UntargetedWrapperMap': {'foo': {'bar': self.original_value}}, + } def test_transform_nested_list(self): input_params = { @@ -275,114 +281,113 @@ class TestInputOutputTransformer(BaseTransformationTest): ], 'UntargetedWrapperList': [ [self.original_value, self.original_value] - ] + ], } targeted_list_shape = { 'TransformMe': { 'type': 'list', - 'member': {'shape': self.target_shape} + 'member': {'shape': self.target_shape}, } } targeted_wrapper_shape = { 'TargetedWrapperList': { 'type': 'list', - 'member': {'shape': 'TransformMe'}} + 'member': {'shape': 'TransformMe'}, + } } self.add_shape(targeted_list_shape) self.add_input_shape(targeted_wrapper_shape) untargeted_list_shape = { - 'LeaveAlone': { - 'type': 'list', - 'member': {'shape': 'String'} - } + 'LeaveAlone': {'type': 'list', 'member': {'shape': 'String'}} } untargeted_wrapper_shape = { 'UntargetedWrapperList': { 'type': 'list', - 'member': {'shape': 'LeaveAlone'}} + 'member': {'shape': 'LeaveAlone'}, + } } self.add_shape(untargeted_list_shape) self.add_input_shape(untargeted_wrapper_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, + params=input_params, + model=self.operation_model.input_shape, transformation=self.transformation, - target_shape=self.target_shape) + target_shape=self.target_shape, + ) assert input_params == { 'TargetedWrapperList': [ [self.transformed_value, self.transformed_value] ], 'UntargetedWrapperList': [ [self.original_value, self.original_value] - ] + ], } def test_transform_incorrect_type_for_structure(self): - input_params = { - 'Structure': 'foo' - } + input_params = {'Structure': 'foo'} input_shape = { 'Structure': { 'type': 'structure', 'members': { 'TransformMe': {'shape': self.target_shape}, - } + }, } } self.add_input_shape(input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, + params=input_params, + model=self.operation_model.input_shape, transformation=self.transformation, - target_shape=self.target_shape) + target_shape=self.target_shape, + ) assert input_params == {'Structure': 'foo'} def test_transform_incorrect_type_for_map(self): - input_params = { - 'Map': 'foo' - } + input_params = {'Map': 'foo'} input_shape = { 'Map': { 'type': 'map', 'key': {'shape': 'String'}, - 'value': {'shape': self.target_shape} + 'value': {'shape': self.target_shape}, } } self.add_input_shape(input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, + params=input_params, + model=self.operation_model.input_shape, transformation=self.transformation, - target_shape=self.target_shape) + target_shape=self.target_shape, + ) assert input_params == {'Map': 'foo'} def test_transform_incorrect_type_for_list(self): - input_params = { - 'List': 'foo' - } + input_params = {'List': 'foo'} input_shape = { - 'List': { - 'type': 'list', - 'member': {'shape': self.target_shape} - } + 'List': {'type': 'list', 'member': {'shape': self.target_shape}} } self.add_input_shape(input_shape) self.transformer.transform( - params=input_params, model=self.operation_model.input_shape, - transformation=self.transformation, target_shape=self.target_shape) + params=input_params, + model=self.operation_model.input_shape, + transformation=self.transformation, + target_shape=self.target_shape, + ) assert input_params == {'List': 'foo'} @@ -402,7 +407,7 @@ class TestTransformAttributeValueInput(BaseTransformAttributeValueTest): input_params = { 'Structure': { 'TransformMe': self.python_value, - 'LeaveAlone': 'unchanged' + 'LeaveAlone': 'unchanged', } } input_shape = { @@ -410,19 +415,22 @@ class TestTransformAttributeValueInput(BaseTransformAttributeValueTest): 'type': 'structure', 'members': { 'TransformMe': {'shape': self.target_shape}, - 'LeaveAlone': {'shape': 'String'} - } + 'LeaveAlone': {'shape': 'String'}, + }, } } self.add_input_shape(input_shape) self.injector.inject_attribute_value_input( - params=input_params, model=self.operation_model) + params=input_params, model=self.operation_model + ) assert input_params == { 'Structure': { 'TransformMe': self.dynamodb_value, - 'LeaveAlone': 'unchanged'}} + 'LeaveAlone': 'unchanged', + } + } class TestTransformAttributeValueOutput(BaseTransformAttributeValueTest): @@ -430,7 +438,7 @@ class TestTransformAttributeValueOutput(BaseTransformAttributeValueTest): parsed = { 'Structure': { 'TransformMe': self.dynamodb_value, - 'LeaveAlone': 'unchanged' + 'LeaveAlone': 'unchanged', } } input_shape = { @@ -438,48 +446,52 @@ class TestTransformAttributeValueOutput(BaseTransformAttributeValueTest): 'type': 'structure', 'members': { 'TransformMe': {'shape': self.target_shape}, - 'LeaveAlone': {'shape': 'String'} - } + 'LeaveAlone': {'shape': 'String'}, + }, } } self.add_input_shape(input_shape) self.injector.inject_attribute_value_output( - parsed=parsed, model=self.operation_model) + parsed=parsed, model=self.operation_model + ) assert parsed == { 'Structure': { 'TransformMe': self.python_value, - 'LeaveAlone': 'unchanged'}} + 'LeaveAlone': 'unchanged', + } + } def test_no_output(self): - service_model = ServiceModel({ - 'operations': { - 'SampleOperation': { - 'name': 'SampleOperation', - 'input': {'shape': 'SampleOperationInputOutput'}, - } - }, - 'shapes': { - 'SampleOperationInput': { - 'type': 'structure', - 'members': {} + service_model = ServiceModel( + { + 'operations': { + 'SampleOperation': { + 'name': 'SampleOperation', + 'input': {'shape': 'SampleOperationInputOutput'}, + } + }, + 'shapes': { + 'SampleOperationInput': { + 'type': 'structure', + 'members': {}, + }, + 'String': {'type': 'string'}, }, - 'String': { - 'type': 'string' - } } - }) + ) operation_model = service_model.operation_model('SampleOperation') parsed = {} self.injector.inject_attribute_value_output( - parsed=parsed, model=operation_model) + parsed=parsed, model=operation_model + ) assert parsed == {} class TestTransformConditionExpression(BaseTransformationTest): def setUp(self): - super(TestTransformConditionExpression, self).setUp() + super().setUp() self.add_shape({'ConditionExpression': {'type': 'string'}}) self.add_shape({'KeyExpression': {'type': 'string'}}) @@ -491,65 +503,73 @@ class TestTransformConditionExpression(BaseTransformationTest): self.build_models() def test_non_condition_input(self): - params = { - 'KeyCondition': 'foo', - 'AttrCondition': 'bar' - } + params = {'KeyCondition': 'foo', 'AttrCondition': 'bar'} self.injector.inject_condition_expressions( - params, self.operation_model) + params, self.operation_model + ) assert params == {'KeyCondition': 'foo', 'AttrCondition': 'bar'} def test_single_attr_condition_expression(self): - params = { - 'AttrCondition': Attr('foo').eq('bar') - } + params = {'AttrCondition': Attr('foo').eq('bar')} self.injector.inject_condition_expressions( - params, self.operation_model) + params, self.operation_model + ) assert params == { 'AttrCondition': '#n0 = :v0', 'ExpressionAttributeNames': {'#n0': 'foo'}, - 'ExpressionAttributeValues': {':v0': 'bar'}} + 'ExpressionAttributeValues': {':v0': 'bar'}, + } def test_single_key_conditon_expression(self): - params = { - 'KeyCondition': Key('foo').eq('bar') - } + params = {'KeyCondition': Key('foo').eq('bar')} self.injector.inject_condition_expressions( - params, self.operation_model) + params, self.operation_model + ) assert params == { 'KeyCondition': '#n0 = :v0', 'ExpressionAttributeNames': {'#n0': 'foo'}, - 'ExpressionAttributeValues': {':v0': 'bar'}} + 'ExpressionAttributeValues': {':v0': 'bar'}, + } def test_key_and_attr_conditon_expression(self): params = { 'KeyCondition': Key('foo').eq('bar'), - 'AttrCondition': Attr('biz').eq('baz') + 'AttrCondition': Attr('biz').eq('baz'), } self.injector.inject_condition_expressions( - params, self.operation_model) + params, self.operation_model + ) assert params == { 'KeyCondition': '#n1 = :v1', 'AttrCondition': '#n0 = :v0', 'ExpressionAttributeNames': {'#n0': 'biz', '#n1': 'foo'}, - 'ExpressionAttributeValues': {':v0': 'baz', ':v1': 'bar'}} + 'ExpressionAttributeValues': {':v0': 'baz', ':v1': 'bar'}, + } def test_key_and_attr_conditon_expression_with_placeholders(self): params = { 'KeyCondition': Key('foo').eq('bar'), 'AttrCondition': Attr('biz').eq('baz'), 'ExpressionAttributeNames': {'#a': 'b'}, - 'ExpressionAttributeValues': {':c': 'd'} + 'ExpressionAttributeValues': {':c': 'd'}, } self.injector.inject_condition_expressions( - params, self.operation_model) + params, self.operation_model + ) assert params == { 'KeyCondition': '#n1 = :v1', 'AttrCondition': '#n0 = :v0', 'ExpressionAttributeNames': { - '#n0': 'biz', '#n1': 'foo', '#a': 'b'}, + '#n0': 'biz', + '#n1': 'foo', + '#a': 'b', + }, 'ExpressionAttributeValues': { - ':v0': 'baz', ':v1': 'bar', ':c': 'd'}} + ':v0': 'baz', + ':v1': 'bar', + ':c': 'd', + }, + } class TestCopyDynamoDBParams(unittest.TestCase): @@ -570,14 +590,17 @@ class TestDynamoDBHighLevelResource(unittest.TestCase): def test_instantiation(self): # Instantiate the class. dynamodb_class = type( - 'dynamodb', (DynamoDBHighLevelResource, ServiceResource), - {'meta': self.meta}) - with mock.patch('boto3.dynamodb.transform.TransformationInjector') \ - as mock_injector: + 'dynamodb', + (DynamoDBHighLevelResource, ServiceResource), + {'meta': self.meta}, + ) + with mock.patch( + 'boto3.dynamodb.transform.TransformationInjector' + ) as mock_injector: with mock.patch( 'boto3.dynamodb.transform.DocumentModifiedShape.' - 'replace_documentation_for_matching_shape') \ - as mock_modify_documentation_method: + 'replace_documentation_for_matching_shape' + ) as mock_modify_documentation_method: dynamodb_class(client=self.client) # It should have fired the following events upon instantiation. @@ -586,31 +609,39 @@ class TestDynamoDBHighLevelResource(unittest.TestCase): mock.call( 'provide-client-params.dynamodb', copy_dynamodb_params, - unique_id='dynamodb-create-params-copy'), + unique_id='dynamodb-create-params-copy', + ), mock.call( 'before-parameter-build.dynamodb', mock_injector.return_value.inject_condition_expressions, - unique_id='dynamodb-condition-expression'), + unique_id='dynamodb-condition-expression', + ), mock.call( 'before-parameter-build.dynamodb', mock_injector.return_value.inject_attribute_value_input, - unique_id='dynamodb-attr-value-input'), + unique_id='dynamodb-attr-value-input', + ), mock.call( 'after-call.dynamodb', mock_injector.return_value.inject_attribute_value_output, - unique_id='dynamodb-attr-value-output'), + unique_id='dynamodb-attr-value-output', + ), mock.call( 'docs.*.dynamodb.*.complete-section', mock_modify_documentation_method, - unique_id='dynamodb-attr-value-docs'), + unique_id='dynamodb-attr-value-docs', + ), mock.call( 'docs.*.dynamodb.*.complete-section', mock_modify_documentation_method, - unique_id='dynamodb-key-expression-docs'), + unique_id='dynamodb-key-expression-docs', + ), mock.call( 'docs.*.dynamodb.*.complete-section', mock_modify_documentation_method, - unique_id='dynamodb-cond-expression-docs')] + unique_id='dynamodb-cond-expression-docs', + ), + ] class TestRegisterHighLevelInterface(unittest.TestCase): diff --git a/tests/unit/dynamodb/test_types.py b/tests/unit/dynamodb/test_types.py index 347f270..0a6c2a0 100644 --- a/tests/unit/dynamodb/test_types.py +++ b/tests/unit/dynamodb/test_types.py @@ -14,10 +14,9 @@ from decimal import Decimal import pytest +from boto3.dynamodb.types import Binary, TypeDeserializer, TypeSerializer from tests import unittest -from boto3.dynamodb.types import Binary, TypeSerializer, TypeDeserializer - class TestBinary(unittest.TestCase): def test_bytes_input(self): @@ -38,7 +37,7 @@ class TestBinary(unittest.TestCase): def test_unicode_throws_error(self): with pytest.raises(TypeError): - Binary(u'\u00e9') + Binary('\u00e9') def test_integer_throws_error(self): with pytest.raises(TypeError): @@ -99,20 +98,21 @@ class TestSerializer(unittest.TestCase): assert self.serializer.serialize(b'\x01') == {'B': b'\x01'} def test_serialize_number_set(self): - serialized_value = self.serializer.serialize(set([1, 2, 3])) + serialized_value = self.serializer.serialize({1, 2, 3}) assert len(serialized_value) == 1 assert 'NS' in serialized_value self.assertCountEqual(serialized_value['NS'], ['1', '2', '3']) def test_serialize_string_set(self): - serialized_value = self.serializer.serialize(set(['foo', 'bar'])) + serialized_value = self.serializer.serialize({'foo', 'bar'}) assert len(serialized_value) == 1 assert 'SS' in serialized_value self.assertCountEqual(serialized_value['SS'], ['foo', 'bar']) def test_serialize_binary_set(self): serialized_value = self.serializer.serialize( - set([Binary(b'\x01'), Binary(b'\x02')])) + {Binary(b'\x01'), Binary(b'\x02')} + ) assert len(serialized_value) == 1 assert 'BS' in serialized_value self.assertCountEqual(serialized_value['BS'], [b'\x01', b'\x02']) @@ -123,7 +123,7 @@ class TestSerializer(unittest.TestCase): assert 'L' in serialized_value self.assertCountEqual( serialized_value['L'], - [{'S': 'foo'}, {'N': '1'}, {'L': [{'N': '1'}]}] + [{'S': 'foo'}, {'N': '1'}, {'L': [{'N': '1'}]}], ) def test_serialize_tuple(self): @@ -132,7 +132,7 @@ class TestSerializer(unittest.TestCase): self.assertIn('L', serialized_value) self.assertCountEqual( serialized_value['L'], - [{'S': 'foo'}, {'N': '1'}, {'L': [{'N': '1'}]}] + [{'S': 'foo'}, {'N': '1'}, {'L': [{'N': '1'}]}], ) def test_serialize_map(self): @@ -140,10 +140,7 @@ class TestSerializer(unittest.TestCase): {'foo': 'bar', 'baz': {'biz': 1}} ) assert serialized_value == { - 'M': { - 'foo': {'S': 'bar'}, - 'baz': {'M': {'biz': {'N': '1'}}} - } + 'M': {'foo': {'S': 'bar'}, 'baz': {'M': {'biz': {'N': '1'}}}} } @@ -178,16 +175,22 @@ class TestDeserializer(unittest.TestCase): assert self.deserializer.deserialize({'B': b'\x00'}) == Binary(b'\x00') def test_deserialize_number_set(self): - assert self.deserializer.deserialize( - {'NS': ['1', '1.25']}) == set([Decimal('1'), Decimal('1.25')]) + assert self.deserializer.deserialize({'NS': ['1', '1.25']}) == { + Decimal('1'), + Decimal('1.25'), + } def test_deserialize_string_set(self): - assert self.deserializer.deserialize( - {'SS': ['foo', 'bar']}) == set(['foo', 'bar']) + assert self.deserializer.deserialize({'SS': ['foo', 'bar']}) == { + 'foo', + 'bar', + } def test_deserialize_binary_set(self): - assert self.deserializer.deserialize( - {'BS': [b'\x00', b'\x01']}) == set([Binary(b'\x00'), Binary(b'\x01')]) + assert self.deserializer.deserialize({'BS': [b'\x00', b'\x01']}) == { + Binary(b'\x00'), + Binary(b'\x01'), + } def test_deserialize_list(self): assert self.deserializer.deserialize( @@ -196,5 +199,10 @@ class TestDeserializer(unittest.TestCase): def test_deserialize_map(self): assert self.deserializer.deserialize( - {'M': {'foo': {'S': 'mystring'}, 'bar': {'M': {'baz': {'N': '1'}}}}} + { + 'M': { + 'foo': {'S': 'mystring'}, + 'bar': {'M': {'baz': {'N': '1'}}}, + } + } ) == {'foo': 'mystring', 'bar': {'baz': Decimal('1')}} diff --git a/tests/unit/ec2/test_createtags.py b/tests/unit/ec2/test_createtags.py index 2c6ee2e..6146791 100644 --- a/tests/unit/ec2/test_createtags.py +++ b/tests/unit/ec2/test_createtags.py @@ -10,10 +10,9 @@ # 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. -from tests import mock, unittest - import boto3.session from boto3.ec2 import createtags +from tests import mock, unittest class TestCreateTags(unittest.TestCase): @@ -21,14 +20,7 @@ class TestCreateTags(unittest.TestCase): self.client = mock.Mock() self.resource = mock.Mock() self.resource.meta.client = self.client - self.ref_tags = [ - 'tag1', - 'tag2', - 'tag3', - 'tag4', - 'tag5', - 'tag6' - ] + self.ref_tags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'] self.resource.Tag.side_effect = self.ref_tags def test_create_tags(self): @@ -37,8 +29,8 @@ class TestCreateTags(unittest.TestCase): 'Tags': [ {'Key': 'key1', 'Value': 'value1'}, {'Key': 'key2', 'Value': 'value2'}, - {'Key': 'key3', 'Value': 'value3'} - ] + {'Key': 'key3', 'Value': 'value3'}, + ], } result_tags = createtags.create_tags(self.resource, **ref_kwargs) @@ -53,7 +45,8 @@ class TestCreateTags(unittest.TestCase): mock.call('foo', 'key3', 'value3'), mock.call('bar', 'key1', 'value1'), mock.call('bar', 'key2', 'value2'), - mock.call('bar', 'key3', 'value3')] + mock.call('bar', 'key3', 'value3'), + ] # Ensure the return values are as expected. assert result_tags == self.ref_tags diff --git a/tests/unit/ec2/test_deletetags.py b/tests/unit/ec2/test_deletetags.py index 57f2277..fff0544 100644 --- a/tests/unit/ec2/test_deletetags.py +++ b/tests/unit/ec2/test_deletetags.py @@ -10,9 +10,8 @@ # 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. -from tests import mock, unittest - from boto3.ec2.deletetags import delete_tags +from tests import mock, unittest class TestDeleteTags(unittest.TestCase): @@ -28,7 +27,7 @@ class TestDeleteTags(unittest.TestCase): 'Tags': [ {'Key': 'key1', 'Value': 'value1'}, {'Key': 'key2', 'Value': 'value2'}, - {'Key': 'key3', 'Value': 'value3'} + {'Key': 'key3', 'Value': 'value3'}, ] } diff --git a/tests/unit/resources/test_action.py b/tests/unit/resources/test_action.py index aee45a5..8923afc 100644 --- a/tests/unit/resources/test_action.py +++ b/tests/unit/resources/test_action.py @@ -12,30 +12,26 @@ # language governing permissions and limitations under the License. import pytest -from boto3.utils import ServiceContext from boto3.resources.action import BatchAction, ServiceAction, WaiterAction from boto3.resources.base import ResourceMeta from boto3.resources.model import Action, Waiter +from boto3.utils import ServiceContext from tests import BaseTestCase, mock class TestServiceActionCall(BaseTestCase): def setUp(self): - super(TestServiceActionCall, self).setUp() + super().setUp() - self.action_def = { - 'request': { - 'operation': 'GetFrobs', - 'params': [] - } - } + self.action_def = {'request': {'operation': 'GetFrobs', 'params': []}} @property def action(self): return Action('test', self.action_def, {}) - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', return_value={} + ) def test_service_action_creates_params(self, params_mock): resource = mock.Mock() resource.meta = ResourceMeta('test', client=mock.Mock()) @@ -46,8 +42,10 @@ class TestServiceActionCall(BaseTestCase): assert params_mock.called - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={'bar': 'baz'}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', + return_value={'bar': 'baz'}, + ) def test_service_action_calls_operation(self, params_mock): resource = mock.Mock() resource.meta = ResourceMeta('test', client=mock.Mock()) @@ -61,8 +59,9 @@ class TestServiceActionCall(BaseTestCase): operation.assert_called_with(foo=1, bar='baz') assert response == 'response' - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', return_value={} + ) @mock.patch('boto3.resources.action.RawHandler') def test_service_action_calls_raw_handler(self, handler_mock, params_mock): resource = mock.Mock() @@ -79,14 +78,14 @@ class TestServiceActionCall(BaseTestCase): handler_mock.assert_called_with(None) handler_mock.return_value.assert_called_with(resource, {}, 'response') - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', return_value={} + ) @mock.patch('boto3.resources.action.ResourceHandler') - def test_service_action_calls_resource_handler(self, handler_mock, params_mock): - self.action_def['resource'] = { - 'type': 'Frob', - 'path': 'Container' - } + def test_service_action_calls_resource_handler( + self, handler_mock, params_mock + ): + self.action_def['resource'] = {'type': 'Frob', 'path': 'Container'} resource = mock.Mock() resource.meta = ResourceMeta('test', client=mock.Mock()) @@ -103,12 +102,13 @@ class TestServiceActionCall(BaseTestCase): service_name='test', service_model=service_model, resource_json_definitions=resource_defs, - service_waiter_model=None + service_waiter_model=None, ) action = ServiceAction( - action_model=action_model, factory=factory, - service_context=service_context + action_model=action_model, + factory=factory, + service_context=service_context, ) handler_mock.return_value.return_value = 'response' @@ -116,17 +116,19 @@ class TestServiceActionCall(BaseTestCase): action(resource) handler_mock.assert_called_with( - search_path='Container', factory=factory, + search_path='Container', + factory=factory, resource_model=action_model.resource, service_context=service_context, - operation_name='GetFrobs' + operation_name='GetFrobs', ) def test_service_action_call_positional_argument(self): def _api_call(*args, **kwargs): if args: raise TypeError( - "%s() only accepts keyword arguments." % 'get_frobs') + "%s() only accepts keyword arguments." % 'get_frobs' + ) resource = mock.Mock() resource.meta = ResourceMeta('test', client=mock.Mock()) @@ -140,21 +142,26 @@ class TestServiceActionCall(BaseTestCase): class TestWaiterActionCall(BaseTestCase): def setUp(self): - super(TestWaiterActionCall, self).setUp() + super().setUp() self.waiter_resource_name = 'wait_until_exists' self.waiter_def = { "waiterName": "FrobExists", "params": [ - {"target": "Frob", "sourceType": "identifier", - "source": "Name"}] + { + "target": "Frob", + "sourceType": "identifier", + "source": "Name", + } + ], } @property def waiter(self): return Waiter('test', self.waiter_def) - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', return_value={} + ) def test_service_waiter_creates_params(self, params_mock): resource = mock.Mock() resource.meta = ResourceMeta('test', client=mock.Mock()) @@ -165,8 +172,10 @@ class TestWaiterActionCall(BaseTestCase): assert params_mock.called - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={'bar': 'baz'}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', + return_value={'bar': 'baz'}, + ) def test_service_action_calls_operation(self, params_mock): resource = mock.Mock() resource.meta = ResourceMeta('test', client=mock.Mock()) @@ -184,14 +193,9 @@ class TestWaiterActionCall(BaseTestCase): class TestBatchActionCall(BaseTestCase): def setUp(self): - super(TestBatchActionCall, self).setUp() + super().setUp() - self.action_def = { - 'request': { - 'operation': 'GetFrobs', - 'params': [] - } - } + self.action_def = {'request': {'operation': 'GetFrobs', 'params': []}} @property def model(self): @@ -209,23 +213,28 @@ class TestBatchActionCall(BaseTestCase): def test_batch_action_creates_parameters_from_items(self): self.action_def['request']['params'] = [ {'target': 'Bucket', 'source': 'data', 'path': 'BucketName'}, - {'target': 'Delete.Objects[].Key', 'source': 'data', - 'path': 'Key'} + { + 'target': 'Delete.Objects[].Key', + 'source': 'data', + 'path': 'Key', + }, ] client = mock.Mock() item1 = mock.Mock() - item1.meta = ResourceMeta('test', client=client, data={ - 'BucketName': 'bucket', - 'Key': 'item1' - }) + item1.meta = ResourceMeta( + 'test', + client=client, + data={'BucketName': 'bucket', 'Key': 'item1'}, + ) item2 = mock.Mock() - item2.meta = ResourceMeta('test', client=client, data={ - 'BucketName': 'bucket', - 'Key': 'item2' - }) + item2.meta = ResourceMeta( + 'test', + client=client, + data={'BucketName': 'bucket', 'Key': 'item2'}, + ) collection = mock.Mock() collection.pages.return_value = [[item1, item2]] @@ -233,15 +242,14 @@ class TestBatchActionCall(BaseTestCase): action = BatchAction(self.model) action(collection) - client.get_frobs.assert_called_with(Bucket='bucket', Delete={ - 'Objects': [ - {'Key': 'item1'}, - {'Key': 'item2'} - ] - }) + client.get_frobs.assert_called_with( + Bucket='bucket', + Delete={'Objects': [{'Key': 'item1'}, {'Key': 'item2'}]}, + ) - @mock.patch('boto3.resources.action.create_request_parameters', - return_value={}) + @mock.patch( + 'boto3.resources.action.create_request_parameters', return_value={} + ) def test_batch_action_skips_operation(self, crp_mock): # In this test we have an item from the collection, but no # parameters are set up. Because of this, we do NOT call @@ -284,8 +292,9 @@ class TestBatchActionCall(BaseTestCase): # Here the call is made with params={}, but they are edited # in-place so we need to compare to the final edited value. - crp_mock.assert_called_with(item, model.request, - params={'foo': 'bar'}, index=0) + crp_mock.assert_called_with( + item, model.request, params={'foo': 'bar'}, index=0 + ) client.get_frobs.assert_called_with(foo='bar') @mock.patch('boto3.resources.action.create_request_parameters') @@ -296,7 +305,8 @@ class TestBatchActionCall(BaseTestCase): def _api_call(*args, **kwargs): if args: raise TypeError( - "%s() only accepts keyword arguments." % 'get_frobs') + "%s() only accepts keyword arguments." % 'get_frobs' + ) crp_mock.side_effect = side_effect diff --git a/tests/unit/resources/test_collection.py b/tests/unit/resources/test_collection.py index 384989d..b9a88cc 100644 --- a/tests/unit/resources/test_collection.py +++ b/tests/unit/resources/test_collection.py @@ -11,23 +11,24 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import pytest - from botocore.hooks import HierarchicalEmitter from botocore.model import ServiceModel -from boto3.utils import ServiceContext -from boto3.resources.collection import ( - CollectionFactory, CollectionManager, ResourceCollection -) from boto3.resources.base import ResourceMeta +from boto3.resources.collection import ( + CollectionFactory, + CollectionManager, + ResourceCollection, +) from boto3.resources.factory import ResourceFactory from boto3.resources.model import Collection +from boto3.utils import ServiceContext from tests import BaseTestCase, mock class TestCollectionFactory(BaseTestCase): def setUp(self): - super(TestCollectionFactory, self).setUp() + super().setUp() self.client = mock.Mock() self.client.can_paginate.return_value = False @@ -46,37 +47,33 @@ class TestCollectionFactory(BaseTestCase): 'Chain': { 'hasMany': { 'Frobs': { - 'request': { - 'operation': 'GetFrobs' - }, - 'resource': { - 'type': 'Frob' - } + 'request': {'operation': 'GetFrobs'}, + 'resource': {'type': 'Frob'}, } } - } + }, } collection_model = Collection( - 'Frobs', resource_defs['Chain']['hasMany']['Frobs'], - resource_defs) + 'Frobs', resource_defs['Chain']['hasMany']['Frobs'], resource_defs + ) service_context = ServiceContext( service_name='test', resource_json_definitions=resource_defs, service_model=self.service_model, - service_waiter_model=None + service_waiter_model=None, ) collection_cls = self.load( resource_name='Chain', collection_model=collection_model, service_context=service_context, - event_emitter=self.event_emitter + event_emitter=self.event_emitter, ) collection = collection_cls( collection_model=collection_model, parent=self.parent, factory=self.resource_factory, - service_context=service_context + service_context=service_context, ) assert collection_cls.__name__ == 'test.Chain.FrobsCollectionManager' @@ -95,48 +92,40 @@ class TestCollectionFactory(BaseTestCase): resource_defs = { 'Frob': { 'batchActions': { - 'Delete': { - 'request': { - 'operation': 'DeleteFrobs' - } - } + 'Delete': {'request': {'operation': 'DeleteFrobs'}} } }, 'Chain': { 'hasMany': { 'Frobs': { - 'request': { - 'operation': 'GetFrobs' - }, - 'resource': { - 'type': 'Frob' - } + 'request': {'operation': 'GetFrobs'}, + 'resource': {'type': 'Frob'}, } } - } + }, } collection_model = Collection( - 'Frobs', resource_defs['Chain']['hasMany']['Frobs'], - resource_defs) + 'Frobs', resource_defs['Chain']['hasMany']['Frobs'], resource_defs + ) service_context = ServiceContext( service_name='test', resource_json_definitions=resource_defs, service_model=self.service_model, - service_waiter_model=None + service_waiter_model=None, ) collection_cls = self.load( resource_name='Chain', collection_model=collection_model, service_context=service_context, - event_emitter=self.event_emitter + event_emitter=self.event_emitter, ) collection = collection_cls( collection_model=collection_model, parent=self.parent, factory=self.resource_factory, - service_context=service_context + service_context=service_context, ) assert hasattr(collection, 'delete') @@ -148,16 +137,12 @@ class TestCollectionFactory(BaseTestCase): class TestResourceCollection(BaseTestCase): def setUp(self): - super(TestResourceCollection, self).setUp() + super().setUp() # Minimal definition so things like repr work self.collection_def = { - 'request': { - 'operation': 'TestOperation' - }, - 'resource': { - 'type': 'Frob' - } + 'request': {'operation': 'TestOperation'}, + 'resource': {'type': 'Frob'}, } self.client = mock.Mock() self.client.can_paginate.return_value = False @@ -167,11 +152,7 @@ class TestResourceCollection(BaseTestCase): self.service_model = ServiceModel({}) def get_collection(self): - resource_defs = { - 'Frob': { - 'identifiers': [] - } - } + resource_defs = {'Frob': {'identifiers': []}} # Build up a resource def identifier list based on what # the collection is expecting to be required from its @@ -182,10 +163,12 @@ class TestResourceCollection(BaseTestCase): resource_def = self.collection_def.get('resource', {}) for identifier in resource_def.get('identifiers', []): resource_defs['Frob']['identifiers'].append( - {'name': identifier['target']}) + {'name': identifier['target']} + ) collection_model = Collection( - 'test', self.collection_def, resource_defs) + 'test', self.collection_def, resource_defs + ) collection = CollectionManager( collection_model=collection_model, @@ -195,8 +178,8 @@ class TestResourceCollection(BaseTestCase): service_name='test', service_model=self.service_model, resource_json_definitions=resource_defs, - service_waiter_model=None - ) + service_waiter_model=None, + ), ) return collection @@ -213,26 +196,24 @@ class TestResourceCollection(BaseTestCase): def test_iteration_non_paginated(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.get_frobs.return_value = { 'Frobs': [ {'Id': 'one'}, {'Id': 'two'}, {'Id': 'three'}, - {'Id': 'four'} + {'Id': 'four'}, ] } collection = self.get_collection() @@ -245,26 +226,24 @@ class TestResourceCollection(BaseTestCase): def test_limit_param_non_paginated(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.get_frobs.return_value = { 'Frobs': [ {'Id': 'one'}, {'Id': 'two'}, {'Id': 'three'}, - {'Id': 'four'} + {'Id': 'four'}, ] } collection = self.get_collection() @@ -277,26 +256,24 @@ class TestResourceCollection(BaseTestCase): def test_limit_method_non_paginated(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.get_frobs.return_value = { 'Frobs': [ {'Id': 'one'}, {'Id': 'two'}, {'Id': 'three'}, - {'Id': 'four'} + {'Id': 'four'}, ] } collection = self.get_collection() @@ -310,13 +287,8 @@ class TestResourceCollection(BaseTestCase): @mock.patch('boto3.resources.collection.ResourceHandler') def test_filters_non_paginated(self, handler): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, - 'resource': { - 'type': 'Frob', - 'identifiers': [] - } + 'request': {'operation': 'GetFrobs'}, + 'resource': {'type': 'Frob', 'identifiers': []}, } self.client.get_frobs.return_value = {} handler.return_value.return_value = [] @@ -329,33 +301,22 @@ class TestResourceCollection(BaseTestCase): def test_page_iterator_returns_pages_of_items(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.can_paginate.return_value = True self.client.get_paginator.return_value.paginate.return_value = [ - { - 'Frobs': [ - {'Id': 'one'}, - {'Id': 'two'} - ] - }, { - 'Frobs': [ - {'Id': 'three'}, - {'Id': 'four'} - ] - } + {'Frobs': [{'Id': 'one'}, {'Id': 'two'}]}, + {'Frobs': [{'Id': 'three'}, {'Id': 'four'}]}, ] collection = self.get_collection() pages = list(collection.limit(3).pages()) @@ -365,19 +326,17 @@ class TestResourceCollection(BaseTestCase): def test_page_iterator_page_size(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.can_paginate.return_value = True paginator = self.client.get_paginator.return_value @@ -387,37 +346,27 @@ class TestResourceCollection(BaseTestCase): list(collection.page_size(5).pages()) paginator.paginate.assert_called_with( - PaginationConfig={'PageSize': 5, 'MaxItems': None}) + PaginationConfig={'PageSize': 5, 'MaxItems': None} + ) def test_iteration_paginated(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.can_paginate.return_value = True self.client.get_paginator.return_value.paginate.return_value = [ - { - 'Frobs': [ - {'Id': 'one'}, - {'Id': 'two'} - ] - }, { - 'Frobs': [ - {'Id': 'three'}, - {'Id': 'four'} - ] - } + {'Frobs': [{'Id': 'one'}, {'Id': 'two'}]}, + {'Frobs': [{'Id': 'three'}, {'Id': 'four'}]}, ] collection = self.get_collection() items = list(collection.all()) @@ -431,37 +380,27 @@ class TestResourceCollection(BaseTestCase): self.client.get_paginator.assert_called_with('get_frobs') paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( - PaginationConfig={'PageSize': None, 'MaxItems': None}) + PaginationConfig={'PageSize': None, 'MaxItems': None} + ) def test_limit_param_paginated(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.can_paginate.return_value = True self.client.get_paginator.return_value.paginate.return_value = [ - { - 'Frobs': [ - {'Id': 'one'}, - {'Id': 'two'} - ] - }, { - 'Frobs': [ - {'Id': 'three'}, - {'Id': 'four'} - ] - } + {'Frobs': [{'Id': 'one'}, {'Id': 'two'}]}, + {'Frobs': [{'Id': 'three'}, {'Id': 'four'}]}, ] collection = self.get_collection() items = list(collection.all().limit(2)) @@ -473,33 +412,22 @@ class TestResourceCollection(BaseTestCase): def test_limit_method_paginated(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.can_paginate.return_value = True self.client.get_paginator.return_value.paginate.return_value = [ - { - 'Frobs': [ - {'Id': 'one'}, - {'Id': 'two'} - ] - }, { - 'Frobs': [ - {'Id': 'three'}, - {'Id': 'four'} - ] - } + {'Frobs': [{'Id': 'one'}, {'Id': 'two'}]}, + {'Frobs': [{'Id': 'three'}, {'Id': 'four'}]}, ] collection = self.get_collection() items = list(collection.all().limit(2)) @@ -521,7 +449,9 @@ class TestResourceCollection(BaseTestCase): paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( PaginationConfig={'PageSize': None, 'MaxItems': 2}, - Param1='foo', Param2=3) + Param1='foo', + Param2=3, + ) @mock.patch('boto3.resources.collection.ResourceHandler') def test_filter_does_not_clobber_existing_list_values(self, handler): @@ -529,19 +459,28 @@ class TestResourceCollection(BaseTestCase): 'request': { 'operation': 'GetFrobs', "params": [ - {"target": "Filters[0].Name", "source": "string", - "value": "frob-id"}, - {"target": "Filters[0].Values[0]", "source": "identifier", - "name": "Id"} - ] + { + "target": "Filters[0].Name", + "source": "string", + "value": "frob-id", + }, + { + "target": "Filters[0].Values[0]", + "source": "identifier", + "name": "Id", + }, + ], }, 'resource': { 'type': 'Frob', 'identifiers': [ - {'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id'} - ] - } + { + 'target': 'Id', + 'source': 'response', + 'path': 'Frobs[].Id', + } + ], + }, } self.client.can_paginate.return_value = True self.client.get_paginator.return_value.paginate.return_value = [] @@ -549,15 +488,18 @@ class TestResourceCollection(BaseTestCase): collection = self.get_collection() self.parent.id = 'my-id' - list(collection.filter( - Filters=[{'Name': 'another-filter', 'Values': ['foo']}])) + list( + collection.filter( + Filters=[{'Name': 'another-filter', 'Values': ['foo']}] + ) + ) paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( PaginationConfig={'PageSize': None, 'MaxItems': None}, Filters=[ {'Values': ['my-id'], 'Name': 'frob-id'}, - {'Values': ['foo'], 'Name': 'another-filter'} - ] + {'Values': ['foo'], 'Name': 'another-filter'}, + ], ) @mock.patch('boto3.resources.collection.ResourceHandler') @@ -571,7 +513,8 @@ class TestResourceCollection(BaseTestCase): paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( - PaginationConfig={'PageSize': 1, 'MaxItems': None}) + PaginationConfig={'PageSize': 1, 'MaxItems': None} + ) @mock.patch('boto3.resources.collection.ResourceHandler') def test_page_size_method(self, handler): @@ -584,30 +527,29 @@ class TestResourceCollection(BaseTestCase): paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( - PaginationConfig={'PageSize': 1, 'MaxItems': None}) + PaginationConfig={'PageSize': 1, 'MaxItems': None} + ) def test_chaining(self): self.collection_def = { - 'request': { - 'operation': 'GetFrobs' - }, + 'request': {'operation': 'GetFrobs'}, 'resource': { 'type': 'Frob', 'identifiers': [ { 'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id' + 'path': 'Frobs[].Id', } - ] - } + ], + }, } self.client.get_frobs.return_value = { 'Frobs': [ {'Id': 'one'}, {'Id': 'two'}, {'Id': 'three'}, - {'Id': 'four'} + {'Id': 'four'}, ] } collection = self.get_collection() @@ -631,7 +573,8 @@ class TestResourceCollection(BaseTestCase): paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( - PaginationConfig={'PageSize': 3, 'MaxItems': 3}, CustomArg=1) + PaginationConfig={'PageSize': 3, 'MaxItems': 3}, CustomArg=1 + ) @mock.patch('boto3.resources.collection.ResourceHandler') def test_chaining_filters_does_not_clobber_list_values(self, handler): @@ -639,19 +582,28 @@ class TestResourceCollection(BaseTestCase): 'request': { 'operation': 'GetFrobs', "params": [ - {"target": "Filters[0].Name", "source": "string", - "value": "frob-id"}, - {"target": "Filters[0].Values[0]", "source": "identifier", - "name": "Id"} - ] + { + "target": "Filters[0].Name", + "source": "string", + "value": "frob-id", + }, + { + "target": "Filters[0].Values[0]", + "source": "identifier", + "name": "Id", + }, + ], }, 'resource': { 'type': 'Frob', 'identifiers': [ - {'target': 'Id', 'source': 'response', - 'path': 'Frobs[].Id'} - ] - } + { + 'target': 'Id', + 'source': 'response', + 'path': 'Frobs[].Id', + } + ], + }, } self.client.can_paginate.return_value = True self.client.get_paginator.return_value.paginate.return_value = [] @@ -660,17 +612,21 @@ class TestResourceCollection(BaseTestCase): self.parent.id = 'my-id' collection = collection.filter( - Filters=[{'Name': 'second-filter', 'Values': ['foo']}]) - list(collection.filter( - Filters=[{'Name': 'third-filter', 'Values': ['bar']}])) + Filters=[{'Name': 'second-filter', 'Values': ['foo']}] + ) + list( + collection.filter( + Filters=[{'Name': 'third-filter', 'Values': ['bar']}] + ) + ) paginator = self.client.get_paginator.return_value paginator.paginate.assert_called_with( PaginationConfig={'PageSize': None, 'MaxItems': None}, Filters=[ {'Values': ['my-id'], 'Name': 'frob-id'}, {'Values': ['foo'], 'Name': 'second-filter'}, - {'Values': ['bar'], 'Name': 'third-filter'} - ] + {'Values': ['bar'], 'Name': 'third-filter'}, + ], ) def test_chained_repr(self): diff --git a/tests/unit/resources/test_collection_smoke.py b/tests/unit/resources/test_collection_smoke.py index 798a2bd..13d8d5e 100644 --- a/tests/unit/resources/test_collection_smoke.py +++ b/tests/unit/resources/test_collection_smoke.py @@ -11,12 +11,11 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import botocore.session -from botocore import xform_name import pytest +from botocore import xform_name -from boto3.session import Session from boto3.resources.model import ResourceModel - +from boto3.session import Session # A list of names that are common names of a pagination parameter. # Note that this list is not comprehensive. It may have to be updated @@ -42,10 +41,11 @@ def operation_looks_paginated(operation_model): """ has_input_param = _shape_has_pagination_param(operation_model.input_shape) has_output_param = _shape_has_pagination_param( - operation_model.output_shape) + operation_model.output_shape + ) # If there is a parameter in either the input or output that # is used in pagination, mark the operation as paginateable. - return (has_input_param and has_output_param) + return has_input_param and has_output_param def _shape_has_pagination_param(shape): @@ -70,17 +70,20 @@ def _collection_test_args(): for service_name in session.get_available_resources(): client = session.client(service_name, region_name='us-east-1') json_resource_model = loader.load_service_model( - service_name, 'resources-1') + service_name, 'resources-1' + ) resource_defs = json_resource_model['resources'] resource_models = [] # Get the service resource model service_resource_model = ResourceModel( - service_name, json_resource_model['service'], resource_defs) + service_name, json_resource_model['service'], resource_defs + ) resource_models.append(service_resource_model) # Generate all of the resource models for a service for resource_name, resource_defintion in resource_defs.items(): - resource_models.append(ResourceModel( - resource_name, resource_defintion, resource_defs)) + resource_models.append( + ResourceModel(resource_name, resource_defintion, resource_defs) + ) for resource_model in resource_models: # Iterate over all of the collections for each resource model # and ensure that the collection has a paginator if it needs one. @@ -88,10 +91,7 @@ def _collection_test_args(): yield (client, service_name, resource_name, collection_model) -@pytest.mark.parametrize( - 'collection_args', - _collection_test_args() -) +@pytest.mark.parametrize('collection_args', _collection_test_args()) def test_all_collections_have_paginators_if_needed(collection_args): # If a collection relies on an operation that is paginated, it # will require a paginator to iterate through all of the resources @@ -108,10 +108,12 @@ def _assert_collection_has_paginator_if_needed( underlying_operation_name = collection_model.request.operation # See if the operation can be paginated from the client. can_paginate_operation = client.can_paginate( - xform_name(underlying_operation_name)) + xform_name(underlying_operation_name) + ) # See if the operation looks paginated. looks_paginated = operation_looks_paginated( - client.meta.service_model.operation_model(underlying_operation_name)) + client.meta.service_model.operation_model(underlying_operation_name) + ) # Make sure that if the operation looks paginated then there is # a paginator for the client to use for the collection. if not can_paginate_operation: diff --git a/tests/unit/resources/test_factory.py b/tests/unit/resources/test_factory.py index 0a675be..756d3f3 100644 --- a/tests/unit/resources/test_factory.py +++ b/tests/unit/resources/test_factory.py @@ -11,25 +11,29 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import pytest - from botocore.model import DenormalizedStructureBuilder, ServiceModel -from tests import BaseTestCase, mock from boto3.exceptions import ResourceLoadException -from boto3.utils import ServiceContext from boto3.resources.base import ServiceResource from boto3.resources.collection import CollectionManager from boto3.resources.factory import ResourceFactory +from boto3.utils import ServiceContext +from tests import BaseTestCase, mock class BaseTestResourceFactory(BaseTestCase): def setUp(self): - super(BaseTestResourceFactory, self).setUp() + super().setUp() self.emitter = mock.Mock() self.factory = ResourceFactory(self.emitter) - def load(self, resource_name, resource_json_definition=None, - resource_json_definitions=None, service_model=None): + def load( + self, + resource_name, + resource_json_definition=None, + resource_json_definitions=None, + service_model=None, + ): if resource_json_definition is None: resource_json_definition = {} if resource_json_definitions is None: @@ -38,13 +42,13 @@ class BaseTestResourceFactory(BaseTestCase): service_name='test', resource_json_definitions=resource_json_definitions, service_model=service_model, - service_waiter_model=None + service_waiter_model=None, ) return self.factory.load_from_definition( resource_name=resource_name, single_resource_json_definition=resource_json_definition, - service_context=service_context + service_context=service_context, ) @@ -81,9 +85,7 @@ class TestResourceFactory(BaseTestResourceFactory): {'name': 'ReceiptHandle'}, ], } - defs = { - 'Message': model - } + defs = {'Message': model} resource = self.load('Message', model, defs)('url', 'handle') @@ -102,9 +104,7 @@ class TestResourceFactory(BaseTestResourceFactory): 'Queue': { 'resource': { 'type': 'Queue', - 'identifiers': [ - {'target': 'Url', 'source': 'input'} - ] + 'identifiers': [{'target': 'Url', 'source': 'input'}], } }, 'Message': { @@ -112,16 +112,13 @@ class TestResourceFactory(BaseTestResourceFactory): 'type': 'Message', 'identifiers': [ {'target': 'QueueUrl', 'source': 'input'}, - {'target': 'Handle', 'source': 'input'} - ] + {'target': 'Handle', 'source': 'input'}, + ], } - } + }, } } - defs = { - 'Queue': {}, - 'Message': {} - } + defs = {'Queue': {}, 'Message': {}} TestResource = self.load('test', model, defs) @@ -135,16 +132,20 @@ class TestResourceFactory(BaseTestResourceFactory): 'request': { 'operation': 'DescribeTest', } - } - } - shape = DenormalizedStructureBuilder().with_members({ - 'ETag': { - 'type': 'string', }, - 'LastModified': { - 'type': 'string' - } - }).build_model() + } + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'ETag': { + 'type': 'string', + }, + 'LastModified': {'type': 'string'}, + } + ) + .build_model() + ) service_model = mock.Mock() service_model.shape_for.return_value = shape @@ -154,11 +155,7 @@ class TestResourceFactory(BaseTestResourceFactory): assert hasattr(TestResource, 'last_modified') def test_factory_renames_on_clobber_identifier(self): - model = { - 'identifiers': [ - {'name': 'Meta'} - ] - } + model = {'identifiers': [{'name': 'Meta'}]} # Each resource has a ``meta`` defined, so this identifier # must be renamed. @@ -168,17 +165,8 @@ class TestResourceFactory(BaseTestResourceFactory): def test_factory_fails_on_clobber_action(self): model = { - 'identifiers': [ - {'name': 'Test'}, - {'name': 'TestAction'} - ], - 'actions': { - 'Test': { - 'request': { - 'operation': 'GetTest' - } - } - } + 'identifiers': [{'name': 'Test'}, {'name': 'TestAction'}], + 'actions': {'Test': {'request': {'operation': 'GetTest'}}}, } # This fails because the resource has an identifier @@ -198,17 +186,13 @@ class TestResourceFactory(BaseTestResourceFactory): def test_non_service_resource_missing_defs(self): # Only services should get dangling defs defs = { - 'Queue': { - 'identifiers': [ - {'name': 'Url'} - ] - }, + 'Queue': {'identifiers': [{'name': 'Url'}]}, 'Message': { 'identifiers': [ {'name': 'QueueUrl'}, - {'name': 'ReceiptHandle'} + {'name': 'ReceiptHandle'}, ] - } + }, } model = defs['Queue'] @@ -221,28 +205,29 @@ class TestResourceFactory(BaseTestResourceFactory): def test_subresource_requires_only_identifier(self): defs = { 'Queue': { - 'identifiers': [ - {'name': 'Url'} - ], + 'identifiers': [{'name': 'Url'}], 'has': { 'Message': { 'resource': { 'type': 'Message', 'identifiers': [ - {'target': 'QueueUrl', 'source': 'identifier', - 'name': 'Url'}, - {'target': 'ReceiptHandle', 'source': 'input'} - ] + { + 'target': 'QueueUrl', + 'source': 'identifier', + 'name': 'Url', + }, + {'target': 'ReceiptHandle', 'source': 'input'}, + ], } } - } + }, }, 'Message': { 'identifiers': [ {'name': 'QueueUrl'}, - {'name': 'ReceiptHandle'} + {'name': 'ReceiptHandle'}, ] - } + }, } model = defs['Queue'] @@ -282,9 +267,7 @@ class TestResourceFactory(BaseTestResourceFactory): model = { 'actions': { 'GetMessageStatus': { - 'request': { - 'operation': 'DescribeMessageStatus' - } + 'request': {'operation': 'DescribeMessageStatus'} } } } @@ -299,18 +282,12 @@ class TestResourceFactory(BaseTestResourceFactory): @mock.patch('boto3.resources.factory.ServiceAction') def test_resource_action_clears_data(self, action_cls): model = { - 'load': { - 'request': { - 'operation': 'DescribeQueue' - } - }, + 'load': {'request': {'operation': 'DescribeQueue'}}, 'actions': { 'GetMessageStatus': { - 'request': { - 'operation': 'DescribeMessageStatus' - } + 'request': {'operation': 'DescribeMessageStatus'} } - } + }, } queue = self.load('Queue', model)() @@ -331,9 +308,7 @@ class TestResourceFactory(BaseTestResourceFactory): model = { 'actions': { 'GetMessageStatus': { - 'request': { - 'operation': 'DescribeMessageStatus' - } + 'request': {'operation': 'DescribeMessageStatus'} } } } @@ -353,37 +328,34 @@ class TestResourceFactory(BaseTestResourceFactory): def test_resource_lazy_loads_properties(self, action_cls): model = { 'shape': 'TestShape', - 'identifiers': [ - {'name': 'Url'} - ], + 'identifiers': [{'name': 'Url'}], 'load': { 'request': { 'operation': 'DescribeTest', } - } + }, } - shape = DenormalizedStructureBuilder().with_members({ - 'ETag': { - 'type': 'string', - 'shape_name': 'ETag' - }, - 'LastModified': { - 'type': 'string', - 'shape_name': 'LastModified' - }, - 'Url': { - 'type': 'string', - 'shape_name': 'Url' - } - }).build_model() + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'ETag': {'type': 'string', 'shape_name': 'ETag'}, + 'LastModified': { + 'type': 'string', + 'shape_name': 'LastModified', + }, + 'Url': {'type': 'string', 'shape_name': 'Url'}, + } + ) + .build_model() + ) service_model = mock.Mock() service_model.shape_for.return_value = shape action = action_cls.return_value action.return_value = {'ETag': 'tag', 'LastModified': 'never'} - resource = self.load( - 'test', model, service_model=service_model)('url') + resource = self.load('test', model, service_model=service_model)('url') # Accessing an identifier should not call load, even if it's in # the shape members. @@ -407,31 +379,30 @@ class TestResourceFactory(BaseTestResourceFactory): def test_resource_lazy_properties_missing_load(self, action_cls): model = { 'shape': 'TestShape', - 'identifiers': [ - {'name': 'Url'} - ] + 'identifiers': [{'name': 'Url'}] # Note the lack of a `load` method. These resources # are usually loaded via a call on a parent resource. } - shape = DenormalizedStructureBuilder().with_members({ - 'ETag': { - 'type': 'string', - }, - 'LastModified': { - 'type': 'string' - }, - 'Url': { - 'type': 'string' - } - }).build_model() + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'ETag': { + 'type': 'string', + }, + 'LastModified': {'type': 'string'}, + 'Url': {'type': 'string'}, + } + ) + .build_model() + ) service_model = mock.Mock() service_model.shape_for.return_value = shape action = action_cls.return_value action.return_value = {'ETag': 'tag', 'LastModified': 'never'} - resource = self.load( - 'test', model, service_model=service_model)('url') + resource = self.load('test', model, service_model=service_model)('url') with pytest.raises(ResourceLoadException): resource.last_modified @@ -440,24 +411,27 @@ class TestResourceFactory(BaseTestResourceFactory): def test_resource_aliases_identifiers(self, action_cls): model = { 'shape': 'TestShape', - 'identifiers': [ - {'name': 'id', 'memberName': 'foo_id'} - ] + 'identifiers': [{'name': 'id', 'memberName': 'foo_id'}], } - shape = DenormalizedStructureBuilder().with_members({ - 'foo_id': { - 'type': 'string', - }, - 'bar': { - 'type': 'string' - }, - }).build_model() + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'foo_id': { + 'type': 'string', + }, + 'bar': {'type': 'string'}, + } + ) + .build_model() + ) service_model = mock.Mock() service_model.shape_for.return_value = shape shape_id = 'baz' - resource = self.load( - 'test', model, service_model=service_model)(shape_id) + resource = self.load('test', model, service_model=service_model)( + shape_id + ) try: assert resource.id == shape_id @@ -474,48 +448,47 @@ class TestResourceFactory(BaseTestResourceFactory): 'resource': { 'type': 'Subnet', 'identifiers': [ - {'target': 'Id', 'source': 'data', - 'path': 'SubnetId'} - ] + { + 'target': 'Id', + 'source': 'data', + 'path': 'SubnetId', + } + ], } }, 'Vpcs': { 'resource': { 'type': 'Vpc', 'identifiers': [ - {'target': 'Id', 'source': 'data', - 'path': 'Vpcs[].Id'} - ] - } - } - } - } - defs = { - 'Subnet': { - 'identifiers': [{'name': 'Id'}] - }, - 'Vpc': { - 'identifiers': [{'name': 'Id'}] - } - } - service_model = ServiceModel({ - 'shapes': { - 'InstanceShape': { - 'type': 'structure', - 'members': { - 'SubnetId': { - 'shape': 'String' - } + { + 'target': 'Id', + 'source': 'data', + 'path': 'Vpcs[].Id', + } + ], } }, - 'String': { - 'type': 'string' + }, + } + defs = { + 'Subnet': {'identifiers': [{'name': 'Id'}]}, + 'Vpc': {'identifiers': [{'name': 'Id'}]}, + } + service_model = ServiceModel( + { + 'shapes': { + 'InstanceShape': { + 'type': 'structure', + 'members': {'SubnetId': {'shape': 'String'}}, + }, + 'String': {'type': 'string'}, } } - }) + ) - resource = self.load('Instance', model, defs, - service_model)('group-id') + resource = self.load('Instance', model, defs, service_model)( + 'group-id' + ) # Load the resource with no data resource.meta.data = {} @@ -527,10 +500,7 @@ class TestResourceFactory(BaseTestResourceFactory): # Load the resource with data to instantiate a reference resource.meta.data = { 'SubnetId': 'abc123', - 'Vpcs': [ - {'Id': 'vpc1'}, - {'Id': 'vpc2'} - ] + 'Vpcs': [{'Id': 'vpc1'}, {'Id': 'vpc2'}], } assert isinstance(resource.subnet, ServiceResource) @@ -546,19 +516,13 @@ class TestResourceFactory(BaseTestResourceFactory): def test_resource_loads_collections(self, mock_model): model = { 'hasMany': { - u'Queues': { - 'request': { - 'operation': 'ListQueues' - }, - 'resource': { - 'type': 'Queue' - } + 'Queues': { + 'request': {'operation': 'ListQueues'}, + 'resource': {'type': 'Queue'}, } } } - defs = { - 'Queue': {} - } + defs = {'Queue': {}} service_model = ServiceModel({}) mock_model.return_value.name = 'queues' @@ -577,16 +541,14 @@ class TestResourceFactory(BaseTestResourceFactory): { "target": "Bucket", "source": "identifier", - "name": "Name" + "name": "Name", } - ] + ], } } } - defs = { - 'Bucket': {} - } + defs = {'Bucket': {}} service_model = ServiceModel({}) resource = self.load('test', model, defs, service_model)() @@ -603,16 +565,14 @@ class TestResourceFactory(BaseTestResourceFactory): { "target": "Bucket", "source": "identifier", - "name": "Name" + "name": "Name", } - ] + ], } } } - defs = { - 'Bucket': {} - } + defs = {'Bucket': {}} service_model = ServiceModel({}) waiter_action = waiter_action_cls.return_value @@ -624,28 +584,20 @@ class TestResourceFactory(BaseTestResourceFactory): class TestResourceFactoryDanglingResource(BaseTestResourceFactory): def setUp(self): - super(TestResourceFactoryDanglingResource, self).setUp() + super().setUp() self.model = { 'has': { 'Queue': { 'resource': { 'type': 'Queue', - 'identifiers': [ - {'target': 'Url', 'source': 'input'} - ] + 'identifiers': [{'target': 'Url', 'source': 'input'}], } } } } - self.defs = { - 'Queue': { - 'identifiers': [ - {'name': 'Url'} - ] - } - } + self.defs = {'Queue': {'identifiers': [{'name': 'Url'}]}} def test_dangling_resources_create_resource_instance(self): resource = self.load('test', self.model, self.defs)() @@ -717,17 +669,20 @@ class TestResourceFactoryDanglingResource(BaseTestResourceFactory): 'resource': { 'type': 'Message', 'identifiers': [ - {'target': 'QueueUrl', 'source': 'identifier', - 'name': 'Url'}, - {'target': 'Handle', 'source': 'input'} - ] + { + 'target': 'QueueUrl', + 'source': 'identifier', + 'name': 'Url', + }, + {'target': 'Handle', 'source': 'input'}, + ], } } - } + }, }, 'Message': { 'identifiers': [{'name': 'QueueUrl'}, {'name': 'Handle'}] - } + }, } resource = self.load('test', self.model, self.defs)() @@ -755,28 +710,35 @@ class TestResourceFactoryDanglingResource(BaseTestResourceFactory): 'resource': { 'type': 'NetworkInterface', 'identifiers': [ - {'target': 'Id', 'source': 'data', - 'path': 'NetworkInterface.Id'} + { + 'target': 'Id', + 'source': 'data', + 'path': 'NetworkInterface.Id', + } ], - 'path': 'NetworkInterface' + 'path': 'NetworkInterface', } } - } + }, }, 'NetworkInterface': { 'identifiers': [{'name': 'Id'}], - 'shape': 'NetworkInterfaceShape' - } + 'shape': 'NetworkInterfaceShape', + }, } self.model = self.defs['Instance'] - shape = DenormalizedStructureBuilder().with_members({ - 'Id': { - 'type': 'string', - }, - 'PublicIp': { - 'type': 'string' - } - }).build_model() + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'Id': { + 'type': 'string', + }, + 'PublicIp': {'type': 'string'}, + } + ) + .build_model() + ) service_model = mock.Mock() service_model.shape_for.return_value = shape @@ -788,9 +750,10 @@ class TestResourceFactoryDanglingResource(BaseTestResourceFactory): instance.meta.data = { 'NetworkInterface': { 'Id': 'network-interface-id', - 'PublicIp': '127.0.0.1' + 'PublicIp': '127.0.0.1', } } + instance.load = mock.Mock(side_effect=set_meta_data) # Now, get the reference and make sure it has its data @@ -802,41 +765,33 @@ class TestResourceFactoryDanglingResource(BaseTestResourceFactory): class TestServiceResourceSubresources(BaseTestResourceFactory): def setUp(self): - super(TestServiceResourceSubresources, self).setUp() + super().setUp() self.model = { 'has': { 'QueueObject': { 'resource': { 'type': 'Queue', - 'identifiers': [ - {'target': 'Url', 'source': 'input'} - ] + 'identifiers': [{'target': 'Url', 'source': 'input'}], } }, 'PriorityQueue': { 'resource': { 'type': 'Queue', - 'identifiers': [ - {'target': 'Url', 'source': 'input'} - ] + 'identifiers': [{'target': 'Url', 'source': 'input'}], } - } + }, } } self.defs = { - 'Queue': { - 'identifiers': [ - {'name': 'Url'} - ] - }, + 'Queue': {'identifiers': [{'name': 'Url'}]}, 'Message': { 'identifiers': [ {'name': 'QueueUrl'}, - {'name': 'ReceiptHandle'} + {'name': 'ReceiptHandle'}, ] - } + }, } def test_subresource_custom_name(self): @@ -872,13 +827,19 @@ class TestServiceResourceSubresources(BaseTestResourceFactory): assert self.emitter.emit.called call_args = self.emitter.emit.call_args # Verify the correct event name emitted. - assert call_args[0][0] == 'creating-resource-class.test.ServiceResource' + assert ( + call_args[0][0] == 'creating-resource-class.test.ServiceResource' + ) # Verify we send out the class attributes dict. actual_class_attrs = sorted(call_args[1]['class_attributes']) assert actual_class_attrs == [ - 'Message', 'PriorityQueue', 'QueueObject', - 'get_available_subresources', 'meta'] + 'Message', + 'PriorityQueue', + 'QueueObject', + 'get_available_subresources', + 'meta', + ] base_classes = sorted(call_args[1]['base_classes']) assert base_classes == [ServiceResource] diff --git a/tests/unit/resources/test_model.py b/tests/unit/resources/test_model.py index c49f29f..b115597 100644 --- a/tests/unit/resources/test_model.py +++ b/tests/unit/resources/test_model.py @@ -13,7 +13,7 @@ from botocore.model import DenormalizedStructureBuilder -from boto3.resources.model import ResourceModel, Action, Collection, Waiter +from boto3.resources.model import Action, Collection, ResourceModel, Waiter from tests import BaseTestCase @@ -24,39 +24,48 @@ class TestModels(BaseTestCase): assert model.name == 'test' def test_resource_shape(self): - model = ResourceModel('test', { - 'shape': 'Frob' - }, {}) + model = ResourceModel('test', {'shape': 'Frob'}, {}) assert model.shape == 'Frob' def test_resource_identifiers(self): - model = ResourceModel('test', { - 'identifiers': [ - {'name': 'one'}, - {'name': 'two', 'memberName': 'three'} - ] - }, {}) + model = ResourceModel( + 'test', + { + 'identifiers': [ + {'name': 'one'}, + {'name': 'two', 'memberName': 'three'}, + ] + }, + {}, + ) assert model.identifiers[0].name == 'one' assert model.identifiers[1].name == 'two' assert model.identifiers[1].member_name == 'three' def test_resource_action_raw(self): - model = ResourceModel('test', { - 'actions': { - 'GetFrobs': { - 'request': { - 'operation': 'GetFrobsOperation', - 'params': [ - {'target': 'FrobId', 'source': 'identifier', - 'name': 'Id'} - ] - }, - 'path': 'Container.Frobs[]' + model = ResourceModel( + 'test', + { + 'actions': { + 'GetFrobs': { + 'request': { + 'operation': 'GetFrobsOperation', + 'params': [ + { + 'target': 'FrobId', + 'source': 'identifier', + 'name': 'Id', + } + ], + }, + 'path': 'Container.Frobs[]', + } } - } - }, {}) + }, + {}, + ) assert isinstance(model.actions, list) assert len(model.actions) == 1 @@ -72,18 +81,20 @@ class TestModels(BaseTestCase): assert action.path == 'Container.Frobs[]' def test_resource_action_response_resource(self): - model = ResourceModel('test', { - 'actions': { - 'GetFrobs': { - 'resource': { - 'type': 'Frob', - 'path': 'Container.Frobs[]' + model = ResourceModel( + 'test', + { + 'actions': { + 'GetFrobs': { + 'resource': { + 'type': 'Frob', + 'path': 'Container.Frobs[]', + } } } - } - }, { - 'Frob': {} - }) + }, + {'Frob': {}}, + ) action = model.actions[0] assert action.resource.type == 'Frob' @@ -92,33 +103,37 @@ class TestModels(BaseTestCase): assert action.resource.model.name == 'Frob' def test_resource_load_action(self): - model = ResourceModel('test', { - 'load': { - 'request': { - 'operation': 'GetFrobInfo' - }, - 'path': '$' - } - }, {}) + model = ResourceModel( + 'test', + {'load': {'request': {'operation': 'GetFrobInfo'}, 'path': '$'}}, + {}, + ) assert isinstance(model.load, Action) assert model.load.request.operation == 'GetFrobInfo' assert model.load.path == '$' def test_resource_batch_action(self): - model = ResourceModel('test', { - 'batchActions': { - 'Delete': { - 'request': { - 'operation': 'DeleteObjects', - 'params': [ - {'target': 'Bucket', 'sourceType': 'identifier', - 'source': 'BucketName'} - ] + model = ResourceModel( + 'test', + { + 'batchActions': { + 'Delete': { + 'request': { + 'operation': 'DeleteObjects', + 'params': [ + { + 'target': 'Bucket', + 'sourceType': 'identifier', + 'source': 'BucketName', + } + ], + } } } - } - }, {}) + }, + {}, + ) assert isinstance(model.batch_actions, list) @@ -128,28 +143,30 @@ class TestModels(BaseTestCase): assert action.request.params[0].target == 'Bucket' def test_sub_resources(self): - model = ResourceModel('test', { - 'has': { - 'RedFrob': { - 'resource': { - 'type': 'Frob', - 'identifiers': [ - {'target': 'Id', 'source': 'input'} - ] - } - }, - 'GreenFrob': { - 'resource': { - 'type': 'Frob', - 'identifiers': [ - {'target': 'Id', 'source': 'input'} - ] - } + model = ResourceModel( + 'test', + { + 'has': { + 'RedFrob': { + 'resource': { + 'type': 'Frob', + 'identifiers': [ + {'target': 'Id', 'source': 'input'} + ], + } + }, + 'GreenFrob': { + 'resource': { + 'type': 'Frob', + 'identifiers': [ + {'target': 'Id', 'source': 'input'} + ], + } + }, } - } - }, { - 'Frob': {} - }) + }, + {'Frob': {}}, + ) assert isinstance(model.subresources, list) assert len(model.subresources) == 2 @@ -172,16 +189,14 @@ class TestModels(BaseTestCase): { 'target': 'Id', 'source': 'data', - 'path': 'FrobId' + 'path': 'FrobId', } - ] + ], } } } } - resource_defs = { - 'Frob': {} - } + resource_defs = {'Frob': {}} model = ResourceModel('test', model_def, resource_defs) assert isinstance(model.references, list) @@ -195,21 +210,18 @@ class TestModels(BaseTestCase): assert ref.resource.identifiers[0].path == 'FrobId' def test_resource_collections(self): - model = ResourceModel('test', { - 'hasMany': { - 'Frobs': { - 'request': { - 'operation': 'GetFrobList' - }, - 'resource': { - 'type': 'Frob', - 'path': 'FrobList[]' + model = ResourceModel( + 'test', + { + 'hasMany': { + 'Frobs': { + 'request': {'operation': 'GetFrobList'}, + 'resource': {'type': 'Frob', 'path': 'FrobList[]'}, } } - } - }, { - 'Frob': {} - }) + }, + {'Frob': {}}, + ) assert isinstance(model.collections, list) assert len(model.collections) == 1 @@ -220,17 +232,24 @@ class TestModels(BaseTestCase): assert model.collections[0].resource.path == 'FrobList[]' def test_waiter(self): - model = ResourceModel('test', { - 'waiters': { - 'Exists': { - 'waiterName': 'ObjectExists', - 'params': [ - {'target': 'Bucket', 'sourceType': 'identifier', - 'source': 'BucketName'} - ] + model = ResourceModel( + 'test', + { + 'waiters': { + 'Exists': { + 'waiterName': 'ObjectExists', + 'params': [ + { + 'target': 'Bucket', + 'sourceType': 'identifier', + 'source': 'BucketName', + } + ], + } } - } - }, {}) + }, + {}, + ) assert isinstance(model.waiters, list) @@ -244,43 +263,43 @@ class TestModels(BaseTestCase): class TestRenaming(BaseTestCase): def test_multiple(self): # This tests a bunch of different renames working together - model = ResourceModel('test', { - 'identifiers': [{'name': 'Foo'}], - 'actions': { - 'Foo': {} - }, - 'has': { - 'Foo': { - 'resource': { - 'type': 'Frob', - 'identifiers': [ - { - 'target': 'Id', - 'source': 'data', - 'path': 'FrobId' - } - ] + model = ResourceModel( + 'test', + { + 'identifiers': [{'name': 'Foo'}], + 'actions': {'Foo': {}}, + 'has': { + 'Foo': { + 'resource': { + 'type': 'Frob', + 'identifiers': [ + { + 'target': 'Id', + 'source': 'data', + 'path': 'FrobId', + } + ], + } } - } + }, + 'hasMany': {'Foo': {}}, + 'waiters': {'Foo': {}}, }, - 'hasMany': { - 'Foo': {} - }, - 'waiters': { - 'Foo': {} - } - }, { - 'Frob': {} - }) + {'Frob': {}}, + ) - shape = DenormalizedStructureBuilder().with_members({ - 'Foo': { - 'type': 'string', - }, - 'Bar': { - 'type': 'string' - } - }).build_model() + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'Foo': { + 'type': 'string', + }, + 'Bar': {'type': 'string'}, + } + ) + .build_model() + ) model.load_rename_map(shape) @@ -301,23 +320,21 @@ class TestRenaming(BaseTestCase): # for the various categories of attributes/properties/methods on the # resource model. def test_meta_beats_identifier(self): - model = ResourceModel('test', { - 'identifiers': [{'name': 'Meta'}] - }, {}) + model = ResourceModel('test', {'identifiers': [{'name': 'Meta'}]}, {}) model.load_rename_map() assert model.identifiers[0].name == 'meta_identifier' def test_load_beats_identifier(self): - model = ResourceModel('test', { - 'identifiers': [{'name': 'Load'}], - 'load': { - 'request': { - 'operation': 'GetFrobs' - } - } - }, {}) + model = ResourceModel( + 'test', + { + 'identifiers': [{'name': 'Load'}], + 'load': {'request': {'operation': 'GetFrobs'}}, + }, + {}, + ) model.load_rename_map() @@ -325,16 +342,14 @@ class TestRenaming(BaseTestCase): assert model.identifiers[0].name == 'load_identifier' def test_identifier_beats_action(self): - model = ResourceModel('test', { - 'identifiers': [{'name': 'foo'}], - 'actions': { - 'Foo': { - 'request': { - 'operation': 'GetFoo' - } - } - } - }, {}) + model = ResourceModel( + 'test', + { + 'identifiers': [{'name': 'foo'}], + 'actions': {'Foo': {'request': {'operation': 'GetFoo'}}}, + }, + {}, + ) model.load_rename_map() @@ -342,29 +357,27 @@ class TestRenaming(BaseTestCase): assert model.actions[0].name == 'foo_action' def test_action_beats_reference(self): - model = ResourceModel('test', { - 'actions': { - 'Foo': { - 'request': { - 'operation': 'GetFoo' + model = ResourceModel( + 'test', + { + 'actions': {'Foo': {'request': {'operation': 'GetFoo'}}}, + 'has': { + 'Foo': { + 'resource': { + 'type': 'Frob', + 'identifiers': [ + { + 'target': 'Id', + 'source': 'data', + 'path': 'FrobId', + } + ], + } } - } + }, }, - 'has': { - 'Foo': { - 'resource': { - 'type': 'Frob', - 'identifiers': [ - { - 'target': 'Id', - 'source': 'data', - 'path': 'FrobId' - } - ] - } - } - } - }, {'Frob': {}}) + {'Frob': {}}, + ) model.load_rename_map() @@ -372,29 +385,27 @@ class TestRenaming(BaseTestCase): assert model.references[0].name == 'foo_reference' def test_reference_beats_collection(self): - model = ResourceModel('test', { - 'has': { - 'Foo': { - 'resource': { - 'type': 'Frob', - 'identifiers': [ - { - 'target': 'Id', - 'source': 'data', - 'path': 'FrobId' - } - ] + model = ResourceModel( + 'test', + { + 'has': { + 'Foo': { + 'resource': { + 'type': 'Frob', + 'identifiers': [ + { + 'target': 'Id', + 'source': 'data', + 'path': 'FrobId', + } + ], + } } - } + }, + 'hasMany': {'Foo': {'resource': {'type': 'Frob'}}}, }, - 'hasMany': { - 'Foo': { - 'resource': { - 'type': 'Frob' - } - } - } - }, {'Frob': {}}) + {'Frob': {}}, + ) model.load_rename_map() @@ -402,18 +413,14 @@ class TestRenaming(BaseTestCase): assert model.collections[0].name == 'foo_collection' def test_collection_beats_waiter(self): - model = ResourceModel('test', { - 'hasMany': { - 'WaitUntilFoo': { - 'resource': { - 'type': 'Frob' - } - } + model = ResourceModel( + 'test', + { + 'hasMany': {'WaitUntilFoo': {'resource': {'type': 'Frob'}}}, + 'waiters': {'Foo': {}}, }, - 'waiters': { - 'Foo': {} - } - }, {'Frob': {}}) + {'Frob': {}}, + ) model.load_rename_map() @@ -421,17 +428,19 @@ class TestRenaming(BaseTestCase): assert model.waiters[0].name == 'wait_until_foo_waiter' def test_waiter_beats_attribute(self): - model = ResourceModel('test', { - 'waiters': { - 'Foo': {} - } - }, {'Frob': {}}) + model = ResourceModel('test', {'waiters': {'Foo': {}}}, {'Frob': {}}) - shape = DenormalizedStructureBuilder().with_members({ - 'WaitUntilFoo': { - 'type': 'string', - } - }).build_model() + shape = ( + DenormalizedStructureBuilder() + .with_members( + { + 'WaitUntilFoo': { + 'type': 'string', + } + } + ) + .build_model() + ) model.load_rename_map(shape) diff --git a/tests/unit/resources/test_params.py b/tests/unit/resources/test_params.py index 84b43fa..cac4b2a 100644 --- a/tests/unit/resources/test_params.py +++ b/tests/unit/resources/test_params.py @@ -16,23 +16,26 @@ from boto3.exceptions import ResourceLoadException from boto3.resources.base import ResourceMeta, ServiceResource from boto3.resources.model import Request from boto3.resources.params import ( - create_request_parameters, build_param_structure + build_param_structure, + create_request_parameters, ) from tests import BaseTestCase, mock class TestServiceActionParams(BaseTestCase): def test_service_action_params_identifier(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'WarehouseUrl', - 'source': 'identifier', - 'name': 'Url' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'WarehouseUrl', + 'source': 'identifier', + 'name': 'Url', + } + ], + } + ) parent = mock.Mock() parent.url = 'w-url' @@ -42,44 +45,44 @@ class TestServiceActionParams(BaseTestCase): assert params['WarehouseUrl'] == 'w-url' def test_service_action_params_data_member(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'WarehouseUrl', - 'source': 'data', - 'path': 'SomeMember' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'WarehouseUrl', + 'source': 'data', + 'path': 'SomeMember', + } + ], + } + ) parent = mock.Mock() - parent.meta = ResourceMeta('test', data={ - 'SomeMember': 'w-url' - }) + parent.meta = ResourceMeta('test', data={'SomeMember': 'w-url'}) params = create_request_parameters(parent, request_model) assert params['WarehouseUrl'] == 'w-url' def test_service_action_params_data_member_missing(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'WarehouseUrl', - 'source': 'data', - 'path': 'SomeMember' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'WarehouseUrl', + 'source': 'data', + 'path': 'SomeMember', + } + ], + } + ) parent = mock.Mock() def load_data(): - parent.meta.data = { - 'SomeMember': 'w-url' - } + parent.meta.data = {'SomeMember': 'w-url'} parent.load.side_effect = load_data parent.meta = ResourceMeta('test') @@ -90,16 +93,18 @@ class TestServiceActionParams(BaseTestCase): assert params['WarehouseUrl'] == 'w-url' def test_service_action_params_data_member_missing_no_load(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'WarehouseUrl', - 'source': 'data', - 'path': 'SomeMember' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'WarehouseUrl', + 'source': 'data', + 'path': 'SomeMember', + } + ], + } + ) # This mock has no ``load`` method. parent = mock.Mock(spec=ServiceResource) @@ -109,26 +114,20 @@ class TestServiceActionParams(BaseTestCase): create_request_parameters(parent, request_model) def test_service_action_params_constants(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'Param1', - 'source': 'string', - 'value': 'param1' - }, - { - 'target': 'Param2', - 'source': 'integer', - 'value': 123 - }, - { - 'target': 'Param3', - 'source': 'boolean', - 'value': True - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'Param1', + 'source': 'string', + 'value': 'param1', + }, + {'target': 'Param2', 'source': 'integer', 'value': 123}, + {'target': 'Param3', 'source': 'boolean', 'value': True}, + ], + } + ) params = create_request_parameters(None, request_model) @@ -137,12 +136,12 @@ class TestServiceActionParams(BaseTestCase): assert params['Param3'] is True def test_service_action_params_input(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - {'target': 'Param1', 'source': 'input'} - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [{'target': 'Param1', 'source': 'input'}], + } + ) params = create_request_parameters(None, request_model) assert params == {} @@ -152,30 +151,29 @@ class TestServiceActionParams(BaseTestCase): assert params == {'param1': 'myinput'} def test_service_action_params_invalid(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'Param1', - 'source': 'invalid' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [{'target': 'Param1', 'source': 'invalid'}], + } + ) with pytest.raises(NotImplementedError): create_request_parameters(None, request_model) def test_service_action_params_list(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'WarehouseUrls[0]', - 'source': 'string', - 'value': 'w-url' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'WarehouseUrls[0]', + 'source': 'string', + 'value': 'w-url', + } + ], + } + ) params = create_request_parameters(None, request_model) @@ -184,26 +182,24 @@ class TestServiceActionParams(BaseTestCase): assert 'w-url' in params['WarehouseUrls'] def test_service_action_params_reuse(self): - request_model = Request({ - 'operation': 'GetFrobs', - 'params': [ - { - 'target': 'Delete.Objects[].Key', - 'source': 'data', - 'path': 'Key' - } - ] - }) + request_model = Request( + { + 'operation': 'GetFrobs', + 'params': [ + { + 'target': 'Delete.Objects[].Key', + 'source': 'data', + 'path': 'Key', + } + ], + } + ) item1 = mock.Mock() - item1.meta = ResourceMeta('test', data={ - 'Key': 'item1' - }) + item1.meta = ResourceMeta('test', data={'Key': 'item1'}) item2 = mock.Mock() - item2.meta = ResourceMeta('test', data={ - 'Key': 'item2' - }) + item2.meta = ResourceMeta('test', data={'Key': 'item2'}) # Here we create params and then re-use it to build up a more # complex structure over multiple calls. @@ -211,12 +207,7 @@ class TestServiceActionParams(BaseTestCase): create_request_parameters(item2, request_model, params=params) assert params == { - 'Delete': { - 'Objects': [ - {'Key': 'item1'}, - {'Key': 'item2'} - ] - } + 'Delete': {'Objects': [{'Key': 'item1'}, {'Key': 'item2'}]} } @@ -247,11 +238,7 @@ class TestStructBuilder(BaseTestCase): assert params['foo']['bar'][0]['baz'] == 123 def test_modify_existing(self): - params = { - 'foo': [ - {'key': 'abc'} - ] - } + params = {'foo': [{'key': 'abc'}]} build_param_structure(params, 'foo[0].secret', 123) assert params['foo'][0]['key'] == 'abc' assert params['foo'][0]['secret'] == 123 @@ -276,5 +263,5 @@ class TestStructBuilder(BaseTestCase): build_param_structure(params, 'foo[*].baz', 123, index) assert params['foo'] == [ {'bar': 123, 'baz': 456}, - {'bar': 789, 'baz': 123} + {'bar': 789, 'baz': 123}, ] diff --git a/tests/unit/resources/test_response.py b/tests/unit/resources/test_response.py index 740f241..47b7bea 100644 --- a/tests/unit/resources/test_response.py +++ b/tests/unit/resources/test_response.py @@ -12,30 +12,28 @@ # language governing permissions and limitations under the License. import pytest -from tests import BaseTestCase, mock -from boto3.utils import ServiceContext from boto3.resources.base import ResourceMeta, ServiceResource -from boto3.resources.model import ResponseResource, Parameter from boto3.resources.factory import ResourceFactory +from boto3.resources.model import Parameter, ResponseResource from boto3.resources.response import ( - build_identifiers, build_empty_response, RawHandler, ResourceHandler + RawHandler, + ResourceHandler, + build_empty_response, + build_identifiers, ) +from boto3.utils import ServiceContext +from tests import BaseTestCase, mock class TestBuildIdentifiers(BaseTestCase): def test_build_identifier_from_res_path_scalar(self): - identifiers = [Parameter(target='Id', source='response', - path='Container.Frob.Id')] + identifiers = [ + Parameter(target='Id', source='response', path='Container.Frob.Id') + ] parent = mock.Mock() params = {} - response = { - 'Container': { - 'Frob': { - 'Id': 'response-path' - } - } - } + response = {'Container': {'Frob': {'Id': 'response-path'}}} values = build_identifiers(identifiers, parent, params, response) @@ -43,20 +41,15 @@ class TestBuildIdentifiers(BaseTestCase): assert values[0][1] == 'response-path' def test_build_identifier_from_res_path_list(self): - identifiers = [Parameter(target='Id', source='response', - path='Container.Frobs[].Id')] + identifiers = [ + Parameter( + target='Id', source='response', path='Container.Frobs[].Id' + ) + ] parent = mock.Mock() params = {} - response = { - 'Container': { - 'Frobs': [ - { - 'Id': 'response-path' - } - ] - } - } + response = {'Container': {'Frobs': [{'Id': 'response-path'}]}} values = build_identifiers(identifiers, parent, params, response) @@ -64,17 +57,12 @@ class TestBuildIdentifiers(BaseTestCase): assert values[0][1] == ['response-path'] def test_build_identifier_from_parent_identifier(self): - identifiers = [Parameter(target='Id', source='identifier', - name='Id')] + identifiers = [Parameter(target='Id', source='identifier', name='Id')] parent = mock.Mock() parent.id = 'identifier' params = {} - response = { - 'Container': { - 'Frobs': [] - } - } + response = {'Container': {'Frobs': []}} values = build_identifiers(identifiers, parent, params, response) @@ -82,19 +70,12 @@ class TestBuildIdentifiers(BaseTestCase): assert values[0][1] == 'identifier' def test_build_identifier_from_parent_data_member(self): - identifiers = [Parameter(target='Id', source='data', - path='Member')] + identifiers = [Parameter(target='Id', source='data', path='Member')] parent = mock.Mock() - parent.meta = ResourceMeta('test', data={ - 'Member': 'data-member' - }) + parent.meta = ResourceMeta('test', data={'Member': 'data-member'}) params = {} - response = { - 'Container': { - 'Frobs': [] - } - } + response = {'Container': {'Frobs': []}} values = build_identifiers(identifiers, parent, params, response) @@ -102,18 +83,13 @@ class TestBuildIdentifiers(BaseTestCase): assert values[0][1] == 'data-member' def test_build_identifier_from_req_param(self): - identifiers = [Parameter(target='Id', source='requestParameter', - path='Param')] + identifiers = [ + Parameter(target='Id', source='requestParameter', path='Param') + ] parent = mock.Mock() - params = { - 'Param': 'request-param' - } - response = { - 'Container': { - 'Frobs': [] - } - } + params = {'Param': 'request-param'} + response = {'Container': {'Frobs': []}} values = build_identifiers(identifiers, parent, params, response) @@ -125,11 +101,7 @@ class TestBuildIdentifiers(BaseTestCase): parent = mock.Mock() params = {} - response = { - 'Container': { - 'Frobs': [] - } - } + response = {'Container': {'Frobs': []}} with pytest.raises(NotImplementedError): build_identifiers(identifiers, parent, params, response) @@ -137,7 +109,7 @@ class TestBuildIdentifiers(BaseTestCase): class TestBuildEmptyResponse(BaseTestCase): def setUp(self): - super(TestBuildEmptyResponse, self).setUp() + super().setUp() self.search_path = '' self.operation_name = 'GetFrobs' @@ -151,8 +123,9 @@ class TestBuildEmptyResponse(BaseTestCase): self.service_model.operation_model.return_value = operation_model def get_response(self): - return build_empty_response(self.search_path, self.operation_name, - self.service_model) + return build_empty_response( + self.search_path, self.operation_name, self.service_model + ) def test_empty_structure(self): self.output_shape.type_name = 'structure' @@ -205,14 +178,10 @@ class TestBuildEmptyResponse(BaseTestCase): container = mock.Mock() container.type_name = 'structure' - container.members = { - 'Frob': frob - } + container.members = {'Frob': frob} self.output_shape.type_name = 'structure' - self.output_shape.members = { - 'Container': container - } + self.output_shape.members = {'Container': container} response = self.get_response() assert response is None @@ -228,9 +197,7 @@ class TestBuildEmptyResponse(BaseTestCase): container.member = frob self.output_shape.type_name = 'structure' - self.output_shape.members = { - 'Container': container - } + self.output_shape.members = {'Container': container} response = self.get_response() assert response is None @@ -242,9 +209,7 @@ class TestBuildEmptyResponse(BaseTestCase): container.type_name = 'invalid' self.output_shape.type_name = 'structure' - self.output_shape.members = { - 'Container': container - } + self.output_shape.members = {'Container': container} with pytest.raises(NotImplementedError): self.get_response() @@ -254,9 +219,7 @@ class TestRawHandler(BaseTestCase): def test_raw_handler_response(self): parent = mock.Mock() params = {} - response = { - 'Id': 'foo' - } + response = {'Id': 'foo'} handler = RawHandler(search_path=None) parsed_response = handler(parent, params, response) @@ -267,14 +230,8 @@ class TestRawHandler(BaseTestCase): def test_raw_handler_response_path(self): parent = mock.Mock() params = {} - frob = { - 'Id': 'foo' - } - response = { - 'Container': { - 'Frob': frob - } - } + frob = {'Id': 'foo'} + response = {'Container': {'Frob': frob}} handler = RawHandler(search_path='Container.Frob') parsed_response = handler(parent, params, response) @@ -284,16 +241,11 @@ class TestRawHandler(BaseTestCase): class TestResourceHandler(BaseTestCase): def setUp(self): - super(TestResourceHandler, self).setUp() + super().setUp() self.identifier_path = '' self.factory = ResourceFactory(mock.Mock()) self.resource_defs = { - 'Frob': { - 'shape': 'Frob', - 'identifiers': [ - {'name': 'Id'} - ] - } + 'Frob': {'shape': 'Frob', 'identifiers': [{'name': 'Id'}]} } self.service_model = mock.Mock() shape = mock.Mock() @@ -304,14 +256,10 @@ class TestResourceHandler(BaseTestCase): frobs.type_name = 'list' container = mock.Mock() container.type_name = 'structure' - container.members = { - 'Frobs': frobs - } + container.members = {'Frobs': frobs} self.output_shape = mock.Mock() self.output_shape.type_name = 'structure' - self.output_shape.members = { - 'Container': container - } + self.output_shape.members = {'Container': container} operation_model = mock.Mock() operation_model.output_shape = self.output_shape self.service_model.operation_model.return_value = operation_model @@ -324,23 +272,28 @@ class TestResourceHandler(BaseTestCase): request_resource_def = { 'type': 'Frob', 'identifiers': [ - {'target': 'Id', 'source': 'response', - 'path': self.identifier_path}, - ] + { + 'target': 'Id', + 'source': 'response', + 'path': self.identifier_path, + }, + ], } resource_model = ResponseResource( - request_resource_def, self.resource_defs) + request_resource_def, self.resource_defs + ) handler = ResourceHandler( - search_path=search_path, factory=self.factory, + search_path=search_path, + factory=self.factory, resource_model=resource_model, service_context=ServiceContext( service_name='myservice', resource_json_definitions=self.resource_defs, service_model=self.service_model, - service_waiter_model=None + service_waiter_model=None, ), - operation_name='GetFrobs' + operation_name='GetFrobs', ) return handler(self.parent, self.params, response) @@ -361,9 +314,7 @@ class TestResourceHandler(BaseTestCase): def test_missing_data_scalar_builds_empty_response(self, build_mock): self.identifier_path = 'Container.Id' search_path = 'Container' - response = { - 'something': 'irrelevant' - } + response = {'something': 'irrelevant'} resources = self.get_resource(search_path, response) @@ -383,7 +334,7 @@ class TestResourceHandler(BaseTestCase): { 'Id': 'another-frob', 'OtherValue': 'foo', - } + }, ] } } @@ -397,12 +348,7 @@ class TestResourceHandler(BaseTestCase): def test_create_resource_list_no_search_path(self): self.identifier_path = '[].Id' search_path = '' - response = [ - { - 'Id': 'a-frob', - 'OtherValue': 'other' - } - ] + response = [{'Id': 'a-frob', 'OtherValue': 'other'}] resources = self.get_resource(search_path, response) @@ -414,9 +360,7 @@ class TestResourceHandler(BaseTestCase): def test_missing_data_list_builds_empty_response(self, build_mock): self.identifier_path = 'Container.Frobs[].Id' search_path = 'Container.Frobs[]' - response = { - 'something': 'irrelevant' - } + response = {'something': 'irrelevant'} resources = self.get_resource(search_path, response) diff --git a/tests/unit/s3/test_inject.py b/tests/unit/s3/test_inject.py index f167832..b460306 100644 --- a/tests/unit/s3/test_inject.py +++ b/tests/unit/s3/test_inject.py @@ -11,9 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import pytest - -from botocore.exceptions import ClientError from botocore.compat import six +from botocore.exceptions import ClientError from boto3.s3 import inject from tests import mock, unittest @@ -28,26 +27,41 @@ class TestInjectTransferMethods(unittest.TestCase): def test_upload_file_proxies_to_transfer_object(self): with mock.patch('boto3.s3.inject.S3Transfer') as transfer: - inject.upload_file(mock.sentinel.CLIENT, - Filename='filename', - Bucket='bucket', Key='key') - transfer_in_context_manager = \ + inject.upload_file( + mock.sentinel.CLIENT, + Filename='filename', + Bucket='bucket', + Key='key', + ) + transfer_in_context_manager = ( transfer.return_value.__enter__.return_value + ) transfer_in_context_manager.upload_file.assert_called_with( - filename='filename', bucket='bucket', key='key', - extra_args=None, callback=None) + filename='filename', + bucket='bucket', + key='key', + extra_args=None, + callback=None, + ) def test_download_file_proxies_to_transfer_object(self): with mock.patch('boto3.s3.inject.S3Transfer') as transfer: inject.download_file( mock.sentinel.CLIENT, - Bucket='bucket', Key='key', - Filename='filename') - transfer_in_context_manager = \ + Bucket='bucket', + Key='key', + Filename='filename', + ) + transfer_in_context_manager = ( transfer.return_value.__enter__.return_value + ) transfer_in_context_manager.download_file.assert_called_with( - bucket='bucket', key='key', filename='filename', - extra_args=None, callback=None) + bucket='bucket', + key='key', + filename='filename', + extra_args=None, + callback=None, + ) class TestBucketLoad(unittest.TestCase): @@ -68,7 +82,7 @@ class TestBucketLoad(unittest.TestCase): inject.bucket_load(self.resource) assert self.resource.meta.data == { 'Name': self.resource.name, - 'CreationDate': 2 + 'CreationDate': 2, } def test_bucket_load_doesnt_find_bucket(self): @@ -84,25 +98,27 @@ class TestBucketLoad(unittest.TestCase): def test_bucket_load_encounters_access_exception(self): self.client.list_buckets.side_effect = ClientError( - {'Error': - {'Code': 'AccessDenied', - 'Message': 'Access Denied'}}, - 'ListBuckets') + {'Error': {'Code': 'AccessDenied', 'Message': 'Access Denied'}}, + 'ListBuckets', + ) inject.bucket_load(self.resource) assert self.resource.meta.data == {} def test_bucket_load_encounters_other_exception(self): self.client.list_buckets.side_effect = ClientError( - {'Error': - {'Code': 'ExpiredToken', - 'Message': 'The provided token has expired.'}}, - 'ListBuckets') + { + 'Error': { + 'Code': 'ExpiredToken', + 'Message': 'The provided token has expired.', + } + }, + 'ListBuckets', + ) with pytest.raises(ClientError): inject.bucket_load(self.resource) class TestBucketTransferMethods(unittest.TestCase): - def setUp(self): self.bucket = mock.Mock(name='my_bucket') self.copy_source = {'Bucket': 'foo', 'Key': 'bar'} @@ -110,38 +126,63 @@ class TestBucketTransferMethods(unittest.TestCase): def test_upload_file_proxies_to_meta_client(self): inject.bucket_upload_file(self.bucket, Filename='foo', Key='key') self.bucket.meta.client.upload_file.assert_called_with( - Filename='foo', Bucket=self.bucket.name, Key='key', - ExtraArgs=None, Callback=None, Config=None) + Filename='foo', + Bucket=self.bucket.name, + Key='key', + ExtraArgs=None, + Callback=None, + Config=None, + ) def test_download_file_proxies_to_meta_client(self): inject.bucket_download_file(self.bucket, Key='key', Filename='foo') self.bucket.meta.client.download_file.assert_called_with( - Bucket=self.bucket.name, Key='key', Filename='foo', - ExtraArgs=None, Callback=None, Config=None) + Bucket=self.bucket.name, + Key='key', + Filename='foo', + ExtraArgs=None, + Callback=None, + Config=None, + ) def test_copy(self): inject.bucket_copy(self.bucket, self.copy_source, Key='key') self.bucket.meta.client.copy.assert_called_with( - CopySource=self.copy_source, Bucket=self.bucket.name, Key='key', - ExtraArgs=None, Callback=None, SourceClient=None, Config=None) + CopySource=self.copy_source, + Bucket=self.bucket.name, + Key='key', + ExtraArgs=None, + Callback=None, + SourceClient=None, + Config=None, + ) def test_upload_fileobj(self): fileobj = six.BytesIO(b'foo') inject.bucket_upload_fileobj(self.bucket, Key='key', Fileobj=fileobj) self.bucket.meta.client.upload_fileobj.assert_called_with( - Bucket=self.bucket.name, Fileobj=fileobj, Key='key', - ExtraArgs=None, Callback=None, Config=None) + Bucket=self.bucket.name, + Fileobj=fileobj, + Key='key', + ExtraArgs=None, + Callback=None, + Config=None, + ) def test_download_fileobj(self): obj = six.BytesIO() inject.bucket_download_fileobj(self.bucket, Key='key', Fileobj=obj) self.bucket.meta.client.download_fileobj.assert_called_with( - Bucket=self.bucket.name, Key='key', Fileobj=obj, ExtraArgs=None, - Callback=None, Config=None) + Bucket=self.bucket.name, + Key='key', + Fileobj=obj, + ExtraArgs=None, + Callback=None, + Config=None, + ) class TestObjectTransferMethods(unittest.TestCase): - def setUp(self): self.obj = mock.Mock(bucket_name='my_bucket', key='my_key') self.copy_source = {'Bucket': 'foo', 'Key': 'bar'} @@ -149,35 +190,60 @@ class TestObjectTransferMethods(unittest.TestCase): def test_upload_file_proxies_to_meta_client(self): inject.object_upload_file(self.obj, Filename='foo') self.obj.meta.client.upload_file.assert_called_with( - Filename='foo', Bucket=self.obj.bucket_name, Key=self.obj.key, - ExtraArgs=None, Callback=None, Config=None) + Filename='foo', + Bucket=self.obj.bucket_name, + Key=self.obj.key, + ExtraArgs=None, + Callback=None, + Config=None, + ) def test_download_file_proxies_to_meta_client(self): inject.object_download_file(self.obj, Filename='foo') self.obj.meta.client.download_file.assert_called_with( - Bucket=self.obj.bucket_name, Key=self.obj.key, Filename='foo', - ExtraArgs=None, Callback=None, Config=None) + Bucket=self.obj.bucket_name, + Key=self.obj.key, + Filename='foo', + ExtraArgs=None, + Callback=None, + Config=None, + ) def test_copy(self): inject.object_copy(self.obj, self.copy_source) self.obj.meta.client.copy.assert_called_with( - CopySource=self.copy_source, Bucket=self.obj.bucket_name, - Key=self.obj.key, ExtraArgs=None, Callback=None, - SourceClient=None, Config=None) + CopySource=self.copy_source, + Bucket=self.obj.bucket_name, + Key=self.obj.key, + ExtraArgs=None, + Callback=None, + SourceClient=None, + Config=None, + ) def test_upload_fileobj(self): fileobj = six.BytesIO(b'foo') inject.object_upload_fileobj(self.obj, Fileobj=fileobj) self.obj.meta.client.upload_fileobj.assert_called_with( - Bucket=self.obj.bucket_name, Fileobj=fileobj, Key=self.obj.key, - ExtraArgs=None, Callback=None, Config=None) + Bucket=self.obj.bucket_name, + Fileobj=fileobj, + Key=self.obj.key, + ExtraArgs=None, + Callback=None, + Config=None, + ) def test_download_fileobj(self): fileobj = six.BytesIO() inject.object_download_fileobj(self.obj, Fileobj=fileobj) self.obj.meta.client.download_fileobj.assert_called_with( - Bucket=self.obj.bucket_name, Key=self.obj.key, Fileobj=fileobj, - ExtraArgs=None, Callback=None, Config=None) + Bucket=self.obj.bucket_name, + Key=self.obj.key, + Fileobj=fileobj, + ExtraArgs=None, + Callback=None, + Config=None, + ) class TestObejctSummaryLoad(unittest.TestCase): @@ -185,9 +251,7 @@ class TestObejctSummaryLoad(unittest.TestCase): self.client = mock.Mock() self.resource = mock.Mock() self.resource.meta.client = self.client - self.head_object_response = { - 'ContentLength': 5, 'ETag': 'my-etag' - } + self.head_object_response = {'ContentLength': 5, 'ETag': 'my-etag'} self.client.head_object.return_value = self.head_object_response def test_object_summary_load(self): diff --git a/tests/unit/s3/test_transfer.py b/tests/unit/s3/test_transfer.py index 9848cf2..9f3456f 100644 --- a/tests/unit/s3/test_transfer.py +++ b/tests/unit/s3/test_transfer.py @@ -11,19 +11,22 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import pytest - -from tests import mock, unittest - -from s3transfer.manager import TransferManager from s3transfer.futures import NonThreadedExecutor +from s3transfer.manager import TransferManager -from boto3.exceptions import RetriesExceededError -from boto3.exceptions import S3UploadFailedError -from boto3.s3.transfer import create_transfer_manager -from boto3.s3.transfer import S3Transfer -from boto3.s3.transfer import OSUtils, TransferConfig, ProgressCallbackInvoker -from boto3.s3.transfer import ClientError, S3TransferRetriesExceededError -from boto3.s3.transfer import KB, MB +from boto3.exceptions import RetriesExceededError, S3UploadFailedError +from boto3.s3.transfer import ( + KB, + MB, + ClientError, + OSUtils, + ProgressCallbackInvoker, + S3Transfer, + S3TransferRetriesExceededError, + TransferConfig, + create_transfer_manager, +) +from tests import mock, unittest class TestCreateTransferManager(unittest.TestCase): @@ -39,8 +42,7 @@ class TestCreateTransferManager(unittest.TestCase): client = object() config = TransferConfig() config.use_threads = False - with mock.patch( - 'boto3.s3.transfer.TransferManager') as manager: + with mock.patch('boto3.s3.transfer.TransferManager') as manager: create_transfer_manager(client, config) assert manager.call_args == mock.call( client, config, None, NonThreadedExecutor @@ -48,8 +50,9 @@ class TestCreateTransferManager(unittest.TestCase): class TestTransferConfig(unittest.TestCase): - def assert_value_of_actual_and_alias(self, config, actual, alias, - ref_value): + def assert_value_of_actual_and_alias( + self, config, actual, alias, ref_value + ): # Ensure that the name set in the underlying TransferConfig (i.e. # the actual) is the correct value. assert getattr(config, actual) == ref_value @@ -60,7 +63,8 @@ class TestTransferConfig(unittest.TestCase): ref_value = 10 config = TransferConfig(max_concurrency=ref_value) self.assert_value_of_actual_and_alias( - config, 'max_request_concurrency', 'max_concurrency', ref_value) + config, 'max_request_concurrency', 'max_concurrency', ref_value + ) # Set a new value using the alias new_value = 15 @@ -68,13 +72,15 @@ class TestTransferConfig(unittest.TestCase): # Make sure it sets the value for both the alias and the actual # value that will be used in the TransferManager self.assert_value_of_actual_and_alias( - config, 'max_request_concurrency', 'max_concurrency', new_value) + config, 'max_request_concurrency', 'max_concurrency', new_value + ) def test_alias_max_io_queue(self): ref_value = 10 config = TransferConfig(max_io_queue=ref_value) self.assert_value_of_actual_and_alias( - config, 'max_io_queue_size', 'max_io_queue', ref_value) + config, 'max_io_queue_size', 'max_io_queue', ref_value + ) # Set a new value using the alias new_value = 15 @@ -82,7 +88,8 @@ class TestTransferConfig(unittest.TestCase): # Make sure it sets the value for both the alias and the actual # value that will be used in the TransferManager self.assert_value_of_actual_and_alias( - config, 'max_io_queue_size', 'max_io_queue', new_value) + config, 'max_io_queue_size', 'max_io_queue', new_value + ) def test_transferconfig_parameters(self): config = TransferConfig( @@ -134,32 +141,40 @@ class TestS3Transfer(unittest.TestCase): def test_upload_file(self): extra_args = {'ACL': 'public-read'} - self.transfer.upload_file('smallfile', 'bucket', 'key', - extra_args=extra_args) + self.transfer.upload_file( + 'smallfile', 'bucket', 'key', extra_args=extra_args + ) self.manager.upload.assert_called_with( - 'smallfile', 'bucket', 'key', extra_args, None) + 'smallfile', 'bucket', 'key', extra_args, None + ) def test_download_file(self): extra_args = { 'SSECustomerKey': 'foo', 'SSECustomerAlgorithm': 'AES256', } - self.transfer.download_file('bucket', 'key', '/tmp/smallfile', - extra_args=extra_args) + self.transfer.download_file( + 'bucket', 'key', '/tmp/smallfile', extra_args=extra_args + ) self.manager.download.assert_called_with( - 'bucket', 'key', '/tmp/smallfile', extra_args, None) + 'bucket', 'key', '/tmp/smallfile', extra_args, None + ) def test_upload_wraps_callback(self): self.transfer.upload_file( - 'smallfile', 'bucket', 'key', callback=self.callback) + 'smallfile', 'bucket', 'key', callback=self.callback + ) self.assert_callback_wrapped_in_subscriber( - self.manager.upload.call_args) + self.manager.upload.call_args + ) def test_download_wraps_callback(self): self.transfer.download_file( - 'bucket', 'key', '/tmp/smallfile', callback=self.callback) + 'bucket', 'key', '/tmp/smallfile', callback=self.callback + ) self.assert_callback_wrapped_in_subscriber( - self.manager.download.call_args) + self.manager.download.call_args + ) def test_propogation_of_retry_error(self): future = mock.Mock() @@ -181,7 +196,8 @@ class TestS3Transfer(unittest.TestCase): def test_can_create_with_extra_configurations(self): transfer = S3Transfer( - client=mock.Mock(), config=TransferConfig(), osutil=OSUtils()) + client=mock.Mock(), config=TransferConfig(), osutil=OSUtils() + ) assert isinstance(transfer, S3Transfer) def test_client_or_manager_is_required(self): diff --git a/tests/unit/test_boto3.py b/tests/unit/test_boto3.py index f85e368..8d9da2a 100644 --- a/tests/unit/test_boto3.py +++ b/tests/unit/test_boto3.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import boto3 - from tests import mock, unittest @@ -34,15 +33,16 @@ class TestBoto3(unittest.TestCase): def test_create_default_session_with_args(self): boto3.setup_default_session( - aws_access_key_id='key', - aws_secret_access_key='secret') + aws_access_key_id='key', aws_secret_access_key='secret' + ) self.Session.assert_called_with( - aws_access_key_id='key', - aws_secret_access_key='secret') + aws_access_key_id='key', aws_secret_access_key='secret' + ) - @mock.patch('boto3.setup_default_session', - wraps=boto3.setup_default_session) + @mock.patch( + 'boto3.setup_default_session', wraps=boto3.setup_default_session + ) def test_client_creates_default_session(self, setup_session): boto3.DEFAULT_SESSION = None @@ -51,8 +51,9 @@ class TestBoto3(unittest.TestCase): assert setup_session.called assert boto3.DEFAULT_SESSION.client.called - @mock.patch('boto3.setup_default_session', - wraps=boto3.setup_default_session) + @mock.patch( + 'boto3.setup_default_session', wraps=boto3.setup_default_session + ) def test_client_uses_existing_session(self, setup_session): boto3.DEFAULT_SESSION = self.Session() @@ -67,10 +68,12 @@ class TestBoto3(unittest.TestCase): boto3.client('sqs', region_name='us-west-2', verify=False) boto3.DEFAULT_SESSION.client.assert_called_with( - 'sqs', region_name='us-west-2', verify=False) + 'sqs', region_name='us-west-2', verify=False + ) - @mock.patch('boto3.setup_default_session', - wraps=boto3.setup_default_session) + @mock.patch( + 'boto3.setup_default_session', wraps=boto3.setup_default_session + ) def test_resource_creates_default_session(self, setup_session): boto3.DEFAULT_SESSION = None @@ -79,8 +82,9 @@ class TestBoto3(unittest.TestCase): assert setup_session.called assert boto3.DEFAULT_SESSION.resource.called - @mock.patch('boto3.setup_default_session', - wraps=boto3.setup_default_session) + @mock.patch( + 'boto3.setup_default_session', wraps=boto3.setup_default_session + ) def test_resource_uses_existing_session(self, setup_session): boto3.DEFAULT_SESSION = self.Session() @@ -95,4 +99,5 @@ class TestBoto3(unittest.TestCase): boto3.resource('sqs', region_name='us-west-2', verify=False) boto3.DEFAULT_SESSION.resource.assert_called_with( - 'sqs', region_name='us-west-2', verify=False) + 'sqs', region_name='us-west-2', verify=False + ) diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 9430115..728feb3 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -11,19 +11,17 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import pytest - from botocore import loaders -from botocore.exceptions import UnknownServiceError from botocore.client import Config +from botocore.exceptions import UnknownServiceError from boto3 import __version__ from boto3.exceptions import ResourceNotExistsError from boto3.session import Session -from tests import mock, BaseTestCase +from tests import BaseTestCase, mock class TestSession(BaseTestCase): - def test_repr(self): bc_session = self.bc_session_cls.return_value bc_session.get_credentials.return_value.access_key = 'abc123' @@ -49,8 +47,9 @@ class TestSession(BaseTestCase): bc_session = self.bc_session_cls.return_value bc_session.get_config_variable.return_value = 'us-west-2' session = Session('abc123', region_name='us-west-2') - bc_session.set_config_variable.assert_called_with('region', - 'us-west-2') + bc_session.set_config_variable.assert_called_with( + 'region', 'us-west-2' + ) assert session.region_name == 'us-west-2' @@ -63,14 +62,15 @@ class TestSession(BaseTestCase): bc_session = self.bc_session_cls.return_value # Set values in constructor - Session(aws_access_key_id='key', - aws_secret_access_key='secret', - aws_session_token='token') + Session( + aws_access_key_id='key', + aws_secret_access_key='secret', + aws_session_token='token', + ) assert self.bc_session_cls.called assert bc_session.set_credentials.called - bc_session.set_credentials.assert_called_with( - 'key', 'secret', 'token') + bc_session.set_credentials.assert_called_with('key', 'secret', 'token') def test_can_get_credentials(self): access_key = 'foo' @@ -88,7 +88,8 @@ class TestSession(BaseTestCase): session = Session( aws_access_key_id=access_key, aws_secret_access_key=secret_key, - aws_session_token=token) + aws_session_token=token, + ) credentials = session.get_credentials() assert credentials.access_key == access_key @@ -100,8 +101,7 @@ class TestSession(BaseTestCase): session = Session(profile_name='foo') - bc_session.set_config_variable.assert_called_with( - 'profile', 'foo') + bc_session.set_config_variable.assert_called_with('profile', 'foo') bc_session.profile = 'foo' # We should also be able to read the value @@ -202,8 +202,9 @@ class TestSession(BaseTestCase): partitions = session.get_available_regions('myservice') bc_session.get_available_regions.assert_called_with( - service_name='myservice', partition_name='aws', - allow_non_regional=False + service_name='myservice', + partition_name='aws', + allow_non_regional=False, ) assert partitions == ['foo'] @@ -213,9 +214,7 @@ class TestSession(BaseTestCase): session = Session(botocore_session=bc_session) partition = session.get_partition_for_region('foo-bar-1') - bc_session.get_partition_for_region.assert_called_with( - 'foo-bar-1' - ) + bc_session.get_partition_for_region.assert_called_with('foo-bar-1') assert partition == 'baz' def test_create_client(self): @@ -231,17 +230,26 @@ class TestSession(BaseTestCase): session.client('sqs', region_name='us-west-2') bc_session.create_client.assert_called_with( - 'sqs', aws_secret_access_key=None, aws_access_key_id=None, - endpoint_url=None, use_ssl=True, aws_session_token=None, - verify=None, region_name='us-west-2', api_version=None, - config=None) + 'sqs', + aws_secret_access_key=None, + aws_access_key_id=None, + endpoint_url=None, + use_ssl=True, + aws_session_token=None, + verify=None, + region_name='us-west-2', + api_version=None, + config=None, + ) def test_create_resource_with_args(self): mock_bc_session = mock.Mock() loader = mock.Mock(spec=loaders.Loader) loader.determine_latest_version.return_value = '2014-11-02' loader.load_service_model.return_value = { - 'resources': [], 'service': []} + 'resources': [], + 'service': [], + } mock_bc_session.get_component.return_value = loader session = Session(botocore_session=mock_bc_session) session.resource_factory.load_from_definition = mock.Mock() @@ -250,10 +258,17 @@ class TestSession(BaseTestCase): session.resource('sqs', verify=False) session.client.assert_called_with( - 'sqs', aws_secret_access_key=None, aws_access_key_id=None, - endpoint_url=None, use_ssl=True, aws_session_token=None, - verify=False, region_name=None, api_version='2014-11-02', - config=mock.ANY) + 'sqs', + aws_secret_access_key=None, + aws_access_key_id=None, + endpoint_url=None, + use_ssl=True, + aws_session_token=None, + verify=False, + region_name=None, + api_version='2014-11-02', + config=mock.ANY, + ) client_config = session.client.call_args[1]['config'] assert client_config.user_agent_extra == 'Resource' assert client_config.signature_version is None @@ -263,7 +278,9 @@ class TestSession(BaseTestCase): loader = mock.Mock(spec=loaders.Loader) loader.determine_latest_version.return_value = '2014-11-02' loader.load_service_model.return_value = { - 'resources': [], 'service': []} + 'resources': [], + 'service': [], + } mock_bc_session.get_component.return_value = loader session = Session(botocore_session=mock_bc_session) session.resource_factory.load_from_definition = mock.Mock() @@ -273,10 +290,17 @@ class TestSession(BaseTestCase): session.resource('sqs', config=config) session.client.assert_called_with( - 'sqs', aws_secret_access_key=None, aws_access_key_id=None, - endpoint_url=None, use_ssl=True, aws_session_token=None, - verify=None, region_name=None, api_version='2014-11-02', - config=mock.ANY) + 'sqs', + aws_secret_access_key=None, + aws_access_key_id=None, + endpoint_url=None, + use_ssl=True, + aws_session_token=None, + verify=None, + region_name=None, + api_version='2014-11-02', + config=mock.ANY, + ) client_config = session.client.call_args[1]['config'] assert client_config.user_agent_extra == 'Resource' assert client_config.signature_version == 'v4' @@ -286,7 +310,9 @@ class TestSession(BaseTestCase): loader = mock.Mock(spec=loaders.Loader) loader.determine_latest_version.return_value = '2014-11-02' loader.load_service_model.return_value = { - 'resources': [], 'service': []} + 'resources': [], + 'service': [], + } mock_bc_session.get_component.return_value = loader session = Session(botocore_session=mock_bc_session) session.resource_factory.load_from_definition = mock.Mock() @@ -296,10 +322,17 @@ class TestSession(BaseTestCase): session.resource('sqs', config=config) session.client.assert_called_with( - 'sqs', aws_secret_access_key=None, aws_access_key_id=None, - endpoint_url=None, use_ssl=True, aws_session_token=None, - verify=None, region_name=None, api_version='2014-11-02', - config=mock.ANY) + 'sqs', + aws_secret_access_key=None, + aws_access_key_id=None, + endpoint_url=None, + use_ssl=True, + aws_session_token=None, + verify=None, + region_name=None, + api_version='2014-11-02', + config=mock.ANY, + ) client_config = session.client.call_args[1]['config'] assert client_config.user_agent_extra == 'foo' assert client_config.signature_version == 'v4' @@ -309,7 +342,9 @@ class TestSession(BaseTestCase): loader = mock.Mock(spec=loaders.Loader) loader.determine_latest_version.return_value = '2014-11-02' loader.load_service_model.return_value = { - 'resources': [], 'service': []} + 'resources': [], + 'service': [], + } mock_bc_session.get_component.return_value = loader session = Session(botocore_session=mock_bc_session) session.resource_factory.load_from_definition = mock.Mock() @@ -317,7 +352,8 @@ class TestSession(BaseTestCase): session.resource('sqs') loader.load_service_model.assert_called_with( - 'sqs', 'resources-1', None) + 'sqs', 'resources-1', None + ) def test_bad_resource_name(self): mock_bc_session = mock.Mock() diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index f0eb1b3..9db884f 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -11,14 +11,14 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import types -from tests import mock, unittest import pytest from boto3 import utils +from tests import mock, unittest -class FakeModule(object): +class FakeModule: @staticmethod def entry_point(**kwargs): return kwargs @@ -29,7 +29,8 @@ class TestUtils(unittest.TestCase): with mock.patch('boto3.utils.import_module') as importer: importer.return_value = FakeModule lazy_function = utils.lazy_call( - 'fakemodule.FakeModule.entry_point') + 'fakemodule.FakeModule.entry_point' + ) assert lazy_function(a=1, b=2) == {'a': 1, 'b': 2} def test_import_module(self): @@ -52,9 +53,11 @@ class TestLazyLoadedWaiterModel(unittest.TestCase): def test_get_waiter_model_is_lazy(self): session = mock.Mock() waiter_model = utils.LazyLoadedWaiterModel( - session, 'myservice', '2014-01-01') + session, 'myservice', '2014-01-01' + ) assert not session.get_waiter_model.called waiter_model.get_waiter('Foo') assert session.get_waiter_model.called session.get_waiter_model.return_value.get_waiter.assert_called_with( - 'Foo') + 'Foo' + )