Compare commits

...

10 Commits

87 changed files with 892 additions and 393 deletions

View File

@ -68,7 +68,7 @@ jobs:
run: |
pip install -r requirements-dev.txt
- name: Lint
run:
run: |
mkdir .mypy_cache
make lint

View File

@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Ruby ${{ matrix.ruby-version }}
uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677
uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Set up Python 3.8

View File

@ -1,6 +1,76 @@
Moto Changelog
==============
5.0.4
-----
Docker Digest for 5.0.4: _sha256:e13e917e563bd1e3bb745b0ce914bdcc3bd4577542e13e1468eac5078774b2aa_
General:
* Lambda: The results of a Dockerless invocation can now be configured.
See http://docs.getmoto.org/en/latest/docs/services/lambda.html
* StepFunctions can now execute the provided StepFunctionMachine (opt-in), instead of returning static data (still the default).
See http://docs.getmoto.org/en/latest/docs/services/stepfunctions.html
New Service:
* ElastiCache:
* list_tags_for_resource()
* ResilienceHub:
* create_app()
* create_app_version_app_component()
* create_app_version_resource()
* create_resiliency_policy()
* describe_app()
* describe_resiliency_policy()
* import_resources_to_draft_app_version()
* list_app_assessments()
* list_app_version_app_components()
* list_app_version_resources()
* list_app_versions()
* list_apps()
* list_resiliency_policies()
* list_tags_for_resource()
* publish_app_version()
* tag_resource()
* untag_resource()
* Workspaces:
* create_tags()
* create_workspace_image()
* create_workspaces()
* deregister_workspace_directory()
* describe_client_properties()
* describe_tags()
* describe_workspace_directories()
* describe_workspace_image_permissions()
* describe_workspace_images()
* describe_workspaces()
* modify_client_properties()
* modify_selfservice_permissions()
* modify_workspace_creation_properties()
* register_workspace_directory()
* update_workspace_image_permission()
Miscellaneous:
* APIGateway: update_usage_plan() now supports some remove-operations
* Autoscaling: describe_auto_scaling_groups() now returns a dynamic CreatedTime
* CloudFormation: Outputs now support Conditions
* CloudFormation: Outputs now return Descriptions
* CognitoIDP: admin_update_user_attributes() and admin_delete_user_attributes now correctly update the UserLastModifiedDate
* DynamoDB: query() no longer requires the LastEvaluatedKey to exist
* EC2: describe_vpc_endpoint_services() now supports all services
* Kinesis: list_streams() now returns StreamSummaries
* Lambda: get_policy() now throws an error when no statements exist
* ResourceGroupsTaggingAPI now supports DynamoDB tables
* ResourceGroupsTaggingAPI now supports SSM documents
* S3: EventBridge notifications are now supported for ObjectRestore:Post
* S3: restore_object() now contains limited validation when supplying both Days- and Type-argument
* S3: select_object_content() now supports Compressed requests and CSV responses
* SecretsManager: list_secrets() now handles negative matches correctly
* SNS: delete_endpoint() is now an idempotent operation, just like AWS
5.0.3
-----
Docker Digest for 5.0.3: _sha256:032d8ead42f289d9700e9bc844c6d264575ad11b3f6c22cc76d65ff638c8c7bd_

View File

@ -2870,7 +2870,7 @@
## elasticache
<details>
<summary>8% implemented</summary>
<summary>9% implemented</summary>
- [ ] add_tags_to_resource
- [ ] authorize_cache_security_group_ingress
@ -2928,7 +2928,7 @@
- [ ] increase_node_groups_in_global_replication_group
- [ ] increase_replica_count
- [ ] list_allowed_node_type_modifications
- [ ] list_tags_for_resource
- [X] list_tags_for_resource
- [ ] modify_cache_cluster
- [ ] modify_cache_parameter_group
- [ ] modify_cache_subnet_group
@ -8015,6 +8015,85 @@
- [X] update_web_acl
</details>
## workspaces
<details>
<summary>20% implemented</summary>
- [ ] associate_connection_alias
- [ ] associate_ip_groups
- [ ] associate_workspace_application
- [ ] authorize_ip_rules
- [ ] copy_workspace_image
- [ ] create_connect_client_add_in
- [ ] create_connection_alias
- [ ] create_ip_group
- [ ] create_standby_workspaces
- [X] create_tags
- [ ] create_updated_workspace_image
- [ ] create_workspace_bundle
- [X] create_workspace_image
- [X] create_workspaces
- [ ] delete_client_branding
- [ ] delete_connect_client_add_in
- [ ] delete_connection_alias
- [ ] delete_ip_group
- [ ] delete_tags
- [ ] delete_workspace_bundle
- [ ] delete_workspace_image
- [ ] deploy_workspace_applications
- [X] deregister_workspace_directory
- [ ] describe_account
- [ ] describe_account_modifications
- [ ] describe_application_associations
- [ ] describe_applications
- [ ] describe_bundle_associations
- [ ] describe_client_branding
- [X] describe_client_properties
- [ ] describe_connect_client_add_ins
- [ ] describe_connection_alias_permissions
- [ ] describe_connection_aliases
- [ ] describe_image_associations
- [ ] describe_ip_groups
- [X] describe_tags
- [ ] describe_workspace_associations
- [ ] describe_workspace_bundles
- [X] describe_workspace_directories
- [X] describe_workspace_image_permissions
- [X] describe_workspace_images
- [ ] describe_workspace_snapshots
- [X] describe_workspaces
- [ ] describe_workspaces_connection_status
- [ ] disassociate_connection_alias
- [ ] disassociate_ip_groups
- [ ] disassociate_workspace_application
- [ ] import_client_branding
- [ ] import_workspace_image
- [ ] list_available_management_cidr_ranges
- [ ] migrate_workspace
- [ ] modify_account
- [ ] modify_certificate_based_auth_properties
- [X] modify_client_properties
- [ ] modify_saml_properties
- [X] modify_selfservice_permissions
- [ ] modify_workspace_access_properties
- [X] modify_workspace_creation_properties
- [ ] modify_workspace_properties
- [ ] modify_workspace_state
- [ ] reboot_workspaces
- [ ] rebuild_workspaces
- [X] register_workspace_directory
- [ ] restore_workspace
- [ ] revoke_ip_rules
- [ ] start_workspaces
- [ ] stop_workspaces
- [ ] terminate_workspaces
- [ ] update_connect_client_add_in
- [ ] update_connection_alias_permission
- [ ] update_rules_of_ip_group
- [ ] update_workspace_bundle
- [X] update_workspace_image_permission
</details>
## Unimplemented:
<details>
@ -8269,7 +8348,6 @@
- worklink
- workmail
- workmailmessageflow
- workspaces
- workspaces-thin-client
- workspaces-web
- xray

View File

@ -20,6 +20,7 @@ init:
lint:
@echo "Running ruff..."
ruff check moto tests
ruff format --check moto tests
@echo "Running pylint..."
pylint -j 0 moto tests
@echo "Running MyPy..."

View File

@ -65,7 +65,8 @@ The following options can also be configured when running the MotoServer:
options = {
"batch": {"use_docker": True},
"lambda": {"use_docker": True}
"lambda": {"use_docker": True},
"stepfunctions": {"execute_state_machine": True}
}
requests.post(f"http://localhost:5000/moto-api/config", json=options)

View File

@ -155,7 +155,7 @@ apigateway
The following PatchOperations are currently supported:
add : Everything except /apiStages/{apidId:stageName}/throttle/ and children
replace: Everything except /apiStages/{apidId:stageName}/throttle/ and children
remove : Nothing yet
remove : Everything except /apiStages/{apidId:stageName}/throttle/ and children
copy : Nothing yet

View File

@ -78,7 +78,7 @@ athena
],
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/athena/query-results",
"http://motoapi.amazonaws.com/moto-api/static/athena/query-results",
json=expected_results,
)
assert resp.status_code == 201

View File

@ -74,7 +74,7 @@ ce
]
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/ce/cost-and-usage-results",
"http://motoapi.amazonaws.com/moto-api/static/ce/cost-and-usage-results",
json=expected_results,
)
assert resp.status_code == 201

View File

@ -77,7 +77,7 @@ elasticache
- [ ] increase_node_groups_in_global_replication_group
- [ ] increase_replica_count
- [ ] list_allowed_node_type_modifications
- [ ] list_tags_for_resource
- [X] list_tags_for_resource
- [ ] modify_cache_cluster
- [ ] modify_cache_parameter_group
- [ ] modify_cache_subnet_group

View File

@ -82,7 +82,7 @@ inspector2
"region": "us-east-1", # This is the default - can be omitted
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/inspector2/findings-results",
"http://motoapi.amazonaws.com/moto-api/static/inspector2/findings-results",
json=findings,
)

View File

@ -81,15 +81,15 @@ lambda
expected_results = {"results": ["test", "test 2"], "region": "us-east-1"}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/lambda-simple/response",
json=expected_results,
)
"http://motoapi.amazonaws.com/moto-api/static/lambda-simple/response",
json=expected_results
)
assert resp.status_code == 201
client = boto3.client("lambda", region_name="us-east-1")
resp = client.invoke(...) # resp["Payload"].read().decode() == "test"
resp = client.invoke(...) # resp["Payload"].read().decode() == "test2"
- [ ] invoke_async
- [ ] invoke_with_response_stream

View File

@ -47,7 +47,7 @@ rds-data
],
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/rds-data/statement-results",
"http://motoapi.amazonaws.com/moto-api/static/rds-data/statement-results",
json=expected_results,
)
assert resp.status_code == 201

View File

@ -22,7 +22,7 @@ resourcegroupstaggingapi
- [ ] start_report_creation
- [X] tag_resources
Only Logs and RDS resources are currently supported
Only DynamoDB, Logs and RDS resources are currently supported
- [ ] untag_resources

View File

@ -114,6 +114,7 @@ s3
- 's3:ObjectCreated:Post'
- 's3:ObjectCreated:Put'
- 's3:ObjectDeleted'
- 's3:ObjectRestore:Post'
- [X] put_bucket_ownership_controls

View File

@ -42,7 +42,7 @@ sagemaker-runtime
],
}
requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/sagemaker/endpoint-results",
"http://motoapi.amazonaws.com/moto-api/static/sagemaker/endpoint-results",
json=expected_results,
)

View File

@ -0,0 +1,96 @@
.. _implementedservice_workspaces:
.. |start-h3| raw:: html
<h3>
.. |end-h3| raw:: html
</h3>
==========
workspaces
==========
.. autoclass:: moto.workspaces.models.WorkSpacesBackend
|start-h3| Implemented features for this service |end-h3|
- [ ] associate_connection_alias
- [ ] associate_ip_groups
- [ ] associate_workspace_application
- [ ] authorize_ip_rules
- [ ] copy_workspace_image
- [ ] create_connect_client_add_in
- [ ] create_connection_alias
- [ ] create_ip_group
- [ ] create_standby_workspaces
- [X] create_tags
- [ ] create_updated_workspace_image
- [ ] create_workspace_bundle
- [X] create_workspace_image
- [X] create_workspaces
- [ ] delete_client_branding
- [ ] delete_connect_client_add_in
- [ ] delete_connection_alias
- [ ] delete_ip_group
- [ ] delete_tags
- [ ] delete_workspace_bundle
- [ ] delete_workspace_image
- [ ] deploy_workspace_applications
- [X] deregister_workspace_directory
Deregister Workspace Directory with the matching ID.
- [ ] describe_account
- [ ] describe_account_modifications
- [ ] describe_application_associations
- [ ] describe_applications
- [ ] describe_bundle_associations
- [ ] describe_client_branding
- [X] describe_client_properties
- [ ] describe_connect_client_add_ins
- [ ] describe_connection_alias_permissions
- [ ] describe_connection_aliases
- [ ] describe_image_associations
- [ ] describe_ip_groups
- [X] describe_tags
- [ ] describe_workspace_associations
- [ ] describe_workspace_bundles
- [X] describe_workspace_directories
Return info on all directories or directories with matching IDs.
- [X] describe_workspace_image_permissions
- [X] describe_workspace_images
- [ ] describe_workspace_snapshots
- [X] describe_workspaces
- [ ] describe_workspaces_connection_status
- [ ] disassociate_connection_alias
- [ ] disassociate_ip_groups
- [ ] disassociate_workspace_application
- [ ] import_client_branding
- [ ] import_workspace_image
- [ ] list_available_management_cidr_ranges
- [ ] migrate_workspace
- [ ] modify_account
- [ ] modify_certificate_based_auth_properties
- [X] modify_client_properties
- [ ] modify_saml_properties
- [X] modify_selfservice_permissions
- [ ] modify_workspace_access_properties
- [X] modify_workspace_creation_properties
- [ ] modify_workspace_properties
- [ ] modify_workspace_state
- [ ] reboot_workspaces
- [ ] rebuild_workspaces
- [X] register_workspace_directory
- [ ] restore_workspace
- [ ] revoke_ip_rules
- [ ] start_workspaces
- [ ] stop_workspaces
- [ ] terminate_workspaces
- [ ] update_connect_client_add_in
- [ ] update_connection_alias_permission
- [ ] update_rules_of_ip_group
- [ ] update_workspace_bundle
- [X] update_workspace_image_permission

View File

@ -1,4 +1,4 @@
from moto.core.decorator import mock_aws as mock_aws
__title__ = "moto"
__version__ = "5.0.4.dev"
__version__ = "5.0.5.dev"

View File

@ -136,9 +136,9 @@ class CertificateAuthority(BaseModel):
"S3ObjectAcl", None
)
if acl is None:
self.revocation_configuration["CrlConfiguration"][
"S3ObjectAcl"
] = "PUBLIC_READ"
self.revocation_configuration["CrlConfiguration"]["S3ObjectAcl"] = (
"PUBLIC_READ"
)
else:
if acl not in ["PUBLIC_READ", "BUCKET_OWNER_FULL_CONTROL"]:
raise InvalidS3ObjectAclInCrlConfiguration(acl)

View File

@ -39,9 +39,9 @@ class APIGatewayResponse(BaseResponse):
).format(api_key_source=api_key_source),
)
def __validate_endpoint_configuration(
def __validate_endpoint_configuration( # type: ignore[return]
self, endpoint_configuration: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
if endpoint_configuration and "types" in endpoint_configuration:
invalid_types = list(
set(endpoint_configuration["types"]) - set(ENDPOINT_CONFIGURATION_TYPES)
@ -57,9 +57,9 @@ class APIGatewayResponse(BaseResponse):
).format(endpoint_type=invalid_types[0]),
)
def restapis(
def restapis( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -105,9 +105,9 @@ class APIGatewayResponse(BaseResponse):
return 200, {}, json.dumps(rest_api.to_dict())
def __validte_rest_patch_operations(
def __validte_rest_patch_operations( # type: ignore[return]
self, patch_operations: List[Dict[str, str]]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
for op in patch_operations:
path = op["path"]
if "apiKeySource" in path:
@ -142,9 +142,9 @@ class APIGatewayResponse(BaseResponse):
return 200, {}, json.dumps(rest_api.to_dict())
def resources(
def resources( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
@ -156,9 +156,9 @@ class APIGatewayResponse(BaseResponse):
json.dumps({"item": [resource.to_dict() for resource in resources]}),
)
def gateway_response(
def gateway_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "PUT":
return self.put_gateway_response()
@ -167,16 +167,16 @@ class APIGatewayResponse(BaseResponse):
elif request.method == "DELETE":
return self.delete_gateway_response()
def gateway_responses(
def gateway_responses( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self.get_gateway_responses()
def resource_individual(
def resource_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
resource_id = self.path.split("/")[-1]
@ -268,9 +268,9 @@ class APIGatewayResponse(BaseResponse):
return 204, {}, json.dumps(method_response.to_json()) # type: ignore[union-attr]
raise Exception(f'Unexpected HTTP method "{self.method}"')
def restapis_authorizers(
def restapis_authorizers( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
restapi_id = url_path_parts[2]
@ -320,9 +320,9 @@ class APIGatewayResponse(BaseResponse):
authorizers = self.backend.get_authorizers(restapi_id)
return 200, {}, json.dumps({"item": [a.to_json() for a in authorizers]})
def request_validators(
def request_validators( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
restapi_id = url_path_parts[2]
@ -342,9 +342,9 @@ class APIGatewayResponse(BaseResponse):
)
return 201, {}, json.dumps(validator.to_dict())
def request_validator_individual(
def request_validator_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
restapi_id = url_path_parts[2]
@ -363,9 +363,9 @@ class APIGatewayResponse(BaseResponse):
)
return 200, {}, json.dumps(validator.to_dict())
def authorizers(
def authorizers( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
restapi_id = url_path_parts[2]
@ -384,9 +384,9 @@ class APIGatewayResponse(BaseResponse):
self.backend.delete_authorizer(restapi_id, authorizer_id)
return 202, {}, "{}"
def restapis_stages(
def restapis_stages( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[2]
@ -417,9 +417,9 @@ class APIGatewayResponse(BaseResponse):
stages = self.backend.get_stages(function_id)
return 200, {}, json.dumps({"item": [s.to_json() for s in stages]})
def restapis_stages_tags(
def restapis_stages_tags( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[4]
@ -437,9 +437,9 @@ class APIGatewayResponse(BaseResponse):
stage.tags.pop(tag, None) # type: ignore[union-attr]
return 200, {}, json.dumps({"item": ""})
def stages(
def stages( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[2]
@ -476,9 +476,9 @@ class APIGatewayResponse(BaseResponse):
}
return 200, headers, json.dumps(body).encode("utf-8")
def integrations(
def integrations( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[2]
@ -534,9 +534,9 @@ class APIGatewayResponse(BaseResponse):
)
return 204, {}, json.dumps(integration_response.to_json())
def integration_responses(
def integration_responses( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[2]
@ -574,9 +574,9 @@ class APIGatewayResponse(BaseResponse):
)
return 204, {}, json.dumps(integration_response.to_json())
def deployments(
def deployments( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
function_id = self.path.replace("/restapis/", "", 1).split("/")[0]
@ -592,9 +592,9 @@ class APIGatewayResponse(BaseResponse):
)
return 201, {}, json.dumps(deployment.to_json())
def individual_deployment(
def individual_deployment( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
function_id = url_path_parts[2]
@ -607,9 +607,9 @@ class APIGatewayResponse(BaseResponse):
deployment = self.backend.delete_deployment(function_id, deployment_id)
return 202, {}, json.dumps(deployment.to_json())
def apikeys(
def apikeys( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -649,9 +649,9 @@ class APIGatewayResponse(BaseResponse):
return 200, {}, json.dumps(apikey_resp)
def usage_plans(
def usage_plans( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
usage_plan_response = self.backend.create_usage_plan(json.loads(self.body))
@ -665,9 +665,9 @@ class APIGatewayResponse(BaseResponse):
json.dumps({"item": [u.to_json() for u in usage_plans_response]}),
)
def usage_plan_individual(
def usage_plan_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
@ -686,9 +686,9 @@ class APIGatewayResponse(BaseResponse):
)
return 200, {}, json.dumps(usage_plan_response.to_json())
def usage_plan_keys(
def usage_plan_keys( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
@ -707,9 +707,9 @@ class APIGatewayResponse(BaseResponse):
json.dumps({"item": [u.to_json() for u in usage_plans_response]}),
)
def usage_plan_key_individual(
def usage_plan_key_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
@ -723,9 +723,9 @@ class APIGatewayResponse(BaseResponse):
self.backend.delete_usage_plan_key(usage_plan_id, key_id)
return 202, {}, "{}"
def domain_names(
def domain_names( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -780,9 +780,9 @@ class APIGatewayResponse(BaseResponse):
msg = f'Method "{self.method}" for API GW domain names not implemented'
return 404, {}, json.dumps({"error": msg})
def models(
def models( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
rest_api_id = self.path.replace("/restapis/", "", 1).split("/")[0]
@ -817,9 +817,9 @@ class APIGatewayResponse(BaseResponse):
return 200, {}, json.dumps(model_info.to_json())
return 200, {}, "{}"
def base_path_mappings(
def base_path_mappings( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
@ -842,9 +842,9 @@ class APIGatewayResponse(BaseResponse):
)
return 201, {}, json.dumps(base_path_mapping_resp.to_json())
def base_path_mapping_individual(
def base_path_mapping_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
@ -866,9 +866,9 @@ class APIGatewayResponse(BaseResponse):
)
return 200, {}, json.dumps(base_path_mapping.to_json())
def vpc_link(
def vpc_link( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
url_path_parts = self.path.split("/")
vpc_link_id = url_path_parts[-1]
@ -880,9 +880,9 @@ class APIGatewayResponse(BaseResponse):
vpc_link = self.backend.get_vpc_link(vpc_link_id=vpc_link_id)
return 200, {}, json.dumps(vpc_link.to_json())
def vpc_links(
def vpc_links( # type: ignore[return]
self, request: Any, full_url: str, headers: Dict[str, str]
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":

View File

@ -63,9 +63,9 @@ class ApiGatewayV2Response(BaseResponse):
if self.method == "DELETE":
return self.delete_cors_configuration()
def route_request_parameter(
def route_request_parameter( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "DELETE":
@ -105,9 +105,9 @@ class ApiGatewayV2Response(BaseResponse):
if self.method == "POST":
return self.create_integration()
def integration_response(
def integration_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "DELETE":
@ -117,9 +117,9 @@ class ApiGatewayV2Response(BaseResponse):
if self.method == "PATCH":
return self.update_integration_response()
def integration_responses(
def integration_responses( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -145,9 +145,9 @@ class ApiGatewayV2Response(BaseResponse):
if self.method == "POST":
return self.create_route()
def route_response(
def route_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "DELETE":
@ -155,9 +155,9 @@ class ApiGatewayV2Response(BaseResponse):
if self.method == "GET":
return self.get_route_response()
def route_responses(
def route_responses( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":

View File

@ -27,9 +27,9 @@ class AppSyncResponse(BaseResponse):
if request.method == "GET":
return self.list_graphql_apis()
def graph_ql_individual(
def graph_ql_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self.get_graphql_api()
@ -45,18 +45,18 @@ class AppSyncResponse(BaseResponse):
if request.method == "GET":
return self.list_api_keys()
def schemacreation(
def schemacreation( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "POST":
return self.start_schema_creation()
if request.method == "GET":
return self.get_schema_creation_status()
def api_key_individual(
def api_key_individual( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "DELETE":
return self.delete_api_key()

View File

@ -259,7 +259,7 @@ class AthenaBackend(BaseBackend):
],
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/athena/query-results",
"http://motoapi.amazonaws.com/moto-api/static/athena/query-results",
json=expected_results,
)
assert resp.status_code == 201

View File

@ -2375,6 +2375,26 @@ class LambdaBackend(BaseBackend):
) -> Optional[Union[str, bytes]]:
"""
Invoking a Function with PackageType=Image is not yet supported.
Invoking a Funcation against Lambda without docker now supports customised responses, the default being `Simple Lambda happy path OK`.
You can use a dedicated API to override this, by configuring a queue of expected results.
A request to `invoke` will take the first result from that queue.
Configure this queue by making an HTTP request to `/moto-api/static/lambda-simple/response`. An example invocation looks like this:
.. sourcecode:: python
expected_results = {"results": ["test", "test 2"], "region": "us-east-1"}
resp = requests.post(
"http://motoapi.amazonaws.com/moto-api/static/lambda-simple/response",
json=expected_results
)
assert resp.status_code == 201
client = boto3.client("lambda", region_name="us-east-1")
resp = client.invoke(...) # resp["Payload"].read().decode() == "test"
resp = client.invoke(...) # resp["Payload"].read().decode() == "test2"
"""
fn = self.get_function(function_name, qualifier)
payload = fn.invoke(body, headers, response_headers)

View File

@ -89,9 +89,9 @@ class LambdaResponse(BaseResponse):
if request.method == "GET":
return self._list_layers()
def layers_version(
def layers_version( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
layer_name = unquote(self.path.split("/")[-3])
layer_version = self.path.split("/")[-1]
@ -100,9 +100,9 @@ class LambdaResponse(BaseResponse):
elif request.method == "GET":
return self._get_layer_version(layer_name, layer_version)
def layers_versions(
def layers_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self._get_layer_versions()
@ -190,9 +190,9 @@ class LambdaResponse(BaseResponse):
else:
raise ValueError("Cannot handle request")
def code_signing_config(
def code_signing_config( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self._get_code_signing_config()
@ -212,9 +212,9 @@ class LambdaResponse(BaseResponse):
else:
raise ValueError("Cannot handle request")
def function_url_config(
def function_url_config( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
http_method = request.method
self.setup_class(request, full_url, headers)
@ -417,9 +417,9 @@ class LambdaResponse(BaseResponse):
return 204, {}, ""
@staticmethod
def _set_configuration_qualifier(
def _set_configuration_qualifier( # type: ignore[misc]
configuration: Dict[str, Any], function_name: str, qualifier: str
) -> Dict[str, Any]: # type: ignore[misc]
) -> Dict[str, Any]:
# Qualifier may be explicitly passed or part of function name or ARN, extract it here
if function_name.startswith("arn:aws"):
# Extract from ARN

View File

@ -23,7 +23,7 @@ class LambdaSimpleBackend(LambdaBackend):
headers: Any,
response_headers: Any,
) -> Optional[Union[str, bytes]]:
default_result = "Simple Lambda happy path OK"
default_result = body or "Simple Lambda happy path OK"
if self.lambda_simple_results_queue:
default_result = self.lambda_simple_results_queue.pop(0)
return str.encode(default_result)

View File

@ -191,7 +191,7 @@ class CostExplorerBackend(BaseBackend):
]
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/ce/cost-and-usage-results",
"http://motoapi.amazonaws.com/moto-api/static/ce/cost-and-usage-results",
json=expected_results,
)
assert resp.status_code == 201

View File

@ -343,8 +343,8 @@ def parse_resource_and_generate_name(
generated_resource_name = generate_resource_name(
resource_type,
resources_map["AWS::StackName"],
logical_id, # type: ignore[arg-type]
resources_map["AWS::StackName"], # type: ignore[arg-type]
logical_id,
)
resource_name_property = resource_name_property_from_type(resource_type)
@ -438,11 +438,11 @@ def parse_and_delete_resource(
)
def parse_condition(
def parse_condition( # type: ignore[return]
condition: Union[Dict[str, Any], bool],
resources_map: "ResourceMap",
condition_map: Dict[str, Any],
) -> bool: # type: ignore[return]
) -> bool:
if isinstance(condition, bool):
return condition

View File

@ -40,18 +40,18 @@ class CloudFrontResponse(BaseResponse):
if request.method == "GET":
return self.list_tags_for_resource()
def origin_access_controls(
def origin_access_controls( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "POST":
return self.create_origin_access_control()
if request.method == "GET":
return self.list_origin_access_controls()
def origin_access_control(
def origin_access_control( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self.get_origin_access_control()
@ -86,9 +86,9 @@ class CloudFrontResponse(BaseResponse):
response = template.render(distributions=distributions)
return 200, {}, response
def individual_distribution(
def individual_distribution( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
distribution_id = full_url.split("/")[-1]
if request.method == "DELETE":

View File

@ -1,4 +1,4 @@
from typing import Dict, List, Optional
from typing import Dict, Optional
from moto.core.base_backend import BackendDict, BaseBackend
from moto.core.common_models import BaseModel

View File

@ -29,9 +29,9 @@ class DataBrewResponse(BaseResponse):
tags = self.parameters.get("Tags")
return json.dumps(
self.databrew_backend.create_recipe(
recipe_name,
recipe_description,
recipe_steps,
recipe_name, # type: ignore[arg-type]
recipe_description, # type: ignore[arg-type]
recipe_steps, # type: ignore[arg-type]
tags, # type: ignore[arg-type]
).as_dict()
)
@ -102,7 +102,7 @@ class DataBrewResponse(BaseResponse):
self.databrew_backend.update_recipe(
recipe_name,
recipe_description,
recipe_description, # type: ignore[arg-type]
recipe_steps, # type: ignore[arg-type]
)
return json.dumps({"Name": recipe_name})
@ -147,8 +147,8 @@ class DataBrewResponse(BaseResponse):
ruleset = self.databrew_backend.update_ruleset(
ruleset_name,
ruleset_description,
ruleset_rules,
ruleset_description, # type: ignore[arg-type]
ruleset_rules, # type: ignore[arg-type]
tags, # type: ignore[arg-type]
)
return json.dumps(ruleset.as_dict())

View File

@ -1,5 +1,5 @@
from collections import OrderedDict
from typing import Any, Dict, List, Optional
from typing import Any, Dict, Optional
from moto.core.base_backend import BackendDict, BaseBackend
from moto.core.common_models import BaseModel

View File

@ -478,8 +478,8 @@ class DynamoDBBackend(BaseBackend):
try:
UpdateExpressionExecutor(
validated_ast,
item,
expression_attribute_names, # type: ignore[arg-type]
item, # type: ignore[arg-type]
expression_attribute_names,
).execute()
except ItemSizeTooLarge:
raise ItemSizeToUpdateTooLarge()

View File

@ -76,9 +76,9 @@ class LocalSecondaryIndex(SecondaryIndex):
}
@staticmethod
def create(
def create( # type: ignore[misc]
dct: Dict[str, Any], table_key_attrs: List[str]
) -> "LocalSecondaryIndex": # type: ignore[misc]
) -> "LocalSecondaryIndex":
return LocalSecondaryIndex(
index_name=dct["IndexName"],
schema=dct["KeySchema"],
@ -114,9 +114,9 @@ class GlobalSecondaryIndex(SecondaryIndex):
}
@staticmethod
def create(
def create( # type: ignore[misc]
dct: Dict[str, Any], table_key_attrs: List[str]
) -> "GlobalSecondaryIndex": # type: ignore[misc]
) -> "GlobalSecondaryIndex":
return GlobalSecondaryIndex(
index_name=dct["IndexName"],
schema=dct["KeySchema"],

View File

@ -257,9 +257,9 @@ class UpdateExpressionFunctionEvaluator(DepthFirstTraverser): # type: ignore[mi
raise NotImplementedError(f"Unsupported function for moto {function_name}")
@classmethod
def get_list_from_ddb_typed_value(
def get_list_from_ddb_typed_value( # type: ignore[misc]
cls, node: DDBTypedValue, function_name: str
) -> DynamoType: # type: ignore[misc]
) -> DynamoType:
assert isinstance(node, DDBTypedValue)
dynamo_value = node.get_value()
assert isinstance(dynamo_value, DynamoType)
@ -324,9 +324,9 @@ class ExecuteOperations(DepthFirstTraverser): # type: ignore[misc]
return dynamo_value
@classmethod
def get_sum(
def get_sum( # type: ignore[misc]
cls, left_operand: DynamoType, right_operand: DynamoType
) -> DDBTypedValue: # type: ignore[misc]
) -> DDBTypedValue:
"""
Args:
left_operand(DynamoType):
@ -341,9 +341,9 @@ class ExecuteOperations(DepthFirstTraverser): # type: ignore[misc]
raise IncorrectOperandType("+", left_operand.type)
@classmethod
def get_subtraction(
def get_subtraction( # type: ignore[misc]
cls, left_operand: DynamoType, right_operand: DynamoType
) -> DDBTypedValue: # type: ignore[misc]
) -> DDBTypedValue:
"""
Args:
left_operand(DynamoType):

View File

@ -166,8 +166,8 @@ class Table(BaseModel):
hash_value = DynamoType(item_attrs.get(self.hash_key_attr)) # type: ignore[arg-type]
if self.has_range_key:
range_value: Optional[DynamoType] = DynamoType(
item_attrs.get(self.range_key_attr)
) # type: ignore[arg-type]
item_attrs.get(self.range_key_attr) # type: ignore[arg-type]
)
else:
range_value = None

View File

@ -129,7 +129,7 @@ class DynamoDBStreamsBackend(BaseBackend):
shard_iterator = ShardIterator(
self,
table.stream_shard,
table.stream_shard, # type: ignore[arg-type]
shard_iterator_type,
sequence_number, # type: ignore[arg-type]
)

View File

@ -25,18 +25,18 @@ class EBSResponse(BaseResponse):
if request.method == "POST":
return self.start_snapshot()
def snapshot_block(
def snapshot_block( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers, use_raw_body=True)
if request.method == "PUT":
return self.put_snapshot_block(full_url, headers)
if request.method == "GET":
return self.get_snapshot_block()
def snapshot_blocks(
def snapshot_blocks( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
return self.list_snapshot_blocks()

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, List
from typing import List
from moto.core.base_backend import BackendDict, BaseBackend

View File

@ -50,9 +50,9 @@ class IamInstanceProfileAssociationBackend:
iam_association_id = random_iam_instance_profile_association_id()
instance_profile = filter_iam_instance_profiles(
self.account_id,
self.account_id, # type: ignore[attr-defined]
iam_instance_profile_arn,
iam_instance_profile_name, # type: ignore[attr-defined]
iam_instance_profile_name,
)
if instance_id in self.iam_instance_profile_associations.keys():
@ -127,9 +127,9 @@ class IamInstanceProfileAssociationBackend:
iam_instance_profile_arn: Optional[str] = None,
) -> IamInstanceProfileAssociation:
instance_profile = filter_iam_instance_profiles(
self.account_id,
self.account_id, # type: ignore[attr-defined]
iam_instance_profile_arn,
iam_instance_profile_name, # type: ignore[attr-defined]
iam_instance_profile_name,
)
iam_instance_profile_association = None

View File

@ -668,8 +668,8 @@ class InstanceBackend:
raise InvalidInstanceTypeError(kwargs["instance_type"])
security_groups = [
self.get_security_group_by_name_or_id(name)
for name in security_group_names # type: ignore[attr-defined]
self.get_security_group_by_name_or_id(name) # type: ignore[attr-defined]
for name in security_group_names
]
for sg_id in kwargs.pop("security_group_ids", []):

View File

@ -489,8 +489,8 @@ class RouteBackend:
route.instance = self.get_instance(instance_id) if instance_id else None # type: ignore[attr-defined]
route.interface = (
self.get_network_interface(interface_id) if interface_id else None
) # type: ignore[attr-defined]
self.get_network_interface(interface_id) if interface_id else None # type: ignore[attr-defined]
)
route.vpc_pcx = (
self.get_vpc_peering_connection(vpc_peering_connection_id) # type: ignore[attr-defined]
if vpc_peering_connection_id

View File

@ -629,9 +629,9 @@ class SecurityGroupBackend:
return results
@staticmethod
def _match_sg_rules(
def _match_sg_rules( # type: ignore[misc]
rules_list: List[SecurityRule], filters: Any
) -> List[SecurityRule]: # type: ignore[misc]
) -> List[SecurityRule]:
results = []
for rule in rules_list:
if rule.match_tags(filters):

View File

@ -235,8 +235,8 @@ class TransitGatewayAttachmentBackend:
if remove_subnet_ids:
tgw_attachment.subnet_ids = [ # type: ignore[attr-defined]
id
for id in tgw_attachment.subnet_ids
if id not in remove_subnet_ids # type: ignore[attr-defined]
for id in tgw_attachment.subnet_ids # type: ignore[attr-defined]
if id not in remove_subnet_ids
]
if options:
@ -359,16 +359,16 @@ class TransitGatewayAttachmentBackend:
# For cross-account peering, must be accepted by the accepter
if (
requester_account_id != accepter_account_id
and self.account_id != accepter_account_id
): # type: ignore[attr-defined]
and self.account_id != accepter_account_id # type: ignore[attr-defined]
):
raise InvalidParameterValueErrorPeeringAttachment(
"accept", transit_gateway_attachment_id
)
if (
requester_region_name != accepter_region_name
and self.region_name != accepter_region_name
): # type: ignore[attr-defined]
and self.region_name != accepter_region_name # type: ignore[attr-defined]
):
raise InvalidParameterValueErrorPeeringAttachment(
"accept", transit_gateway_attachment_id
)
@ -393,16 +393,16 @@ class TransitGatewayAttachmentBackend:
if (
requester_account_id != accepter_account_id
and self.account_id != accepter_account_id
): # type: ignore[attr-defined]
and self.account_id != accepter_account_id # type: ignore[attr-defined]
):
raise InvalidParameterValueErrorPeeringAttachment(
"reject", transit_gateway_attachment_id
)
if (
requester_region_name != accepter_region_name
and self.region_name != accepter_region_name
): # type: ignore[attr-defined]
and self.region_name != accepter_region_name # type: ignore[attr-defined]
):
raise InvalidParameterValueErrorPeeringAttachment(
"reject", transit_gateway_attachment_id
)

View File

@ -1044,15 +1044,17 @@ class VPCBackend:
# Return sensible defaults, for services that do not offer a custom implementation
for aws_service in AWS_ENDPOINT_SERVICES:
if aws_service not in COVERED_ENDPOINT_SERVICES:
service_configs = BaseBackend.default_vpc_endpoint_service_factory(region, zones, aws_service)
service_configs = BaseBackend.default_vpc_endpoint_service_factory(
region, zones, aws_service
)
DEFAULT_VPC_ENDPOINT_SERVICES[region].extend(service_configs)
return DEFAULT_VPC_ENDPOINT_SERVICES[region]
@staticmethod
def _matches_service_by_tags(
def _matches_service_by_tags( # type: ignore[misc]
service: Dict[str, Any], filter_item: Dict[str, Any]
) -> bool: # type: ignore[misc]
) -> bool:
"""Return True if service tags are not filtered by their tags.
Note that the API specifies a key of "Values" for a filter, but
@ -1084,11 +1086,11 @@ class VPCBackend:
return matched
@staticmethod
def _filter_endpoint_services(
def _filter_endpoint_services( # type: ignore[misc]
service_names_filters: List[str],
filters: List[Dict[str, Any]],
services: List[Dict[str, Any]],
) -> List[Dict[str, Any]]: # type: ignore[misc]
) -> List[Dict[str, Any]]:
"""Return filtered list of VPC endpoint services."""
if not service_names_filters and not filters:
return services
@ -1161,8 +1163,8 @@ class VPCBackend:
The DryRun parameter is ignored.
"""
default_services = self._collect_default_endpoint_services(
self.account_id,
region, # type: ignore[attr-defined]
self.account_id, # type: ignore[attr-defined]
region,
)
custom_services = [x.to_dict() for x in self.configurations.values()] # type: ignore
all_services = default_services + custom_services

View File

@ -403,9 +403,9 @@ class ECRBackend(BaseBackend):
self.tagger = TaggingService(tag_name="tags")
@staticmethod
def default_vpc_endpoint_service(
def default_vpc_endpoint_service( # type: ignore[misc]
service_region: str, zones: List[str]
) -> List[Dict[str, Any]]: # type: ignore[misc]
) -> List[Dict[str, Any]]:
"""Default VPC endpoint service."""
docker_endpoint = {
"AcceptanceRequired": False,

View File

@ -1207,11 +1207,11 @@ class EC2ContainerServiceBackend(BaseBackend):
return task_definition
@staticmethod
def _validate_container_defs(
def _validate_container_defs( # type: ignore[misc]
memory: Optional[str],
container_definitions: List[Dict[str, Any]],
requires_compatibilities: Optional[List[str]],
) -> None: # type: ignore[misc]
) -> None:
# The capitalised keys are passed by Cloudformation
for cd in container_definitions:
if "name" not in cd and "Name" not in cd:
@ -1356,9 +1356,9 @@ class EC2ContainerServiceBackend(BaseBackend):
return tasks
@staticmethod
def _calculate_task_resource_requirements(
def _calculate_task_resource_requirements( # type: ignore[misc]
task_definition: TaskDefinition,
) -> Dict[str, Any]: # type: ignore[misc]
) -> Dict[str, Any]:
resource_requirements: Dict[str, Any] = {
"CPU": 0,
"MEMORY": 0,
@ -1400,10 +1400,10 @@ class EC2ContainerServiceBackend(BaseBackend):
return resource_requirements
@staticmethod
def _can_be_placed(
def _can_be_placed( # type: ignore[misc]
container_instance: ContainerInstance,
task_resource_requirements: Dict[str, Any],
) -> bool: # type: ignore[misc]
) -> bool:
"""
:param container_instance: The container instance trying to be placed onto

View File

@ -405,7 +405,7 @@ class ELBResponse(BaseResponse):
subnets = params.get("Subnets")
all_subnets = self.elb_backend.attach_load_balancer_to_subnets(
load_balancer_name,
load_balancer_name, # type: ignore[arg-type]
subnets, # type: ignore[arg-type]
)
template = self.response_template(ATTACH_LB_TO_SUBNETS_TEMPLATE)
@ -417,7 +417,7 @@ class ELBResponse(BaseResponse):
subnets = params.get("Subnets")
all_subnets = self.elb_backend.detach_load_balancer_from_subnets(
load_balancer_name,
load_balancer_name, # type: ignore[arg-type]
subnets, # type: ignore[arg-type]
)
template = self.response_template(DETACH_LB_FROM_SUBNETS_TEMPLATE)

View File

@ -455,9 +455,9 @@ class EmrSecurityGroupManager:
pass
@staticmethod
def _render_rules(
def _render_rules( # type: ignore[misc]
rules: Any, managed_groups: Dict[str, Any]
) -> List[Dict[str, Any]]: # type: ignore[misc]
) -> List[Dict[str, Any]]:
rendered_rules = copy.deepcopy(rules)
for rule in rendered_rules:
rule["group_name_or_id"] = managed_groups[rule["group_name_or_id"]].id

View File

@ -1389,9 +1389,9 @@ class EventsBackend(BaseBackend):
)
@staticmethod
def _condition_param_to_stmt_condition(
def _condition_param_to_stmt_condition( # type: ignore[misc]
condition: Optional[Dict[str, Any]],
) -> Optional[Dict[str, Any]]: # type: ignore[misc]
) -> Optional[Dict[str, Any]]:
if condition:
key = condition["Key"]
value = condition["Value"]

View File

@ -413,9 +413,9 @@ class FirehoseBackend(BaseBackend):
}
@staticmethod
def put_http_records(
def put_http_records( # type: ignore[misc]
http_destination: Dict[str, Any], records: List[Dict[str, bytes]]
) -> List[Dict[str, str]]: # type: ignore[misc]
) -> List[Dict[str, str]]:
"""Put records to a HTTP destination."""
# Mostly copied from localstack
url = http_destination["EndpointConfiguration"]["Url"]

View File

@ -37,9 +37,9 @@ class GlacierResponse(BaseResponse):
self.setup_class(request, full_url, headers)
return self._vault_response(request, full_url, headers)
def _vault_response(
def _vault_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
method = request.method
vault_name = vault_from_glacier_url(full_url)
@ -103,9 +103,9 @@ class GlacierResponse(BaseResponse):
self.setup_class(request, full_url, headers)
return self._vault_archive_individual_response(request, full_url, headers)
def _vault_archive_individual_response(
def _vault_archive_individual_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
method = request.method
vault_name = full_url.split("/")[-3]
archive_id = full_url.split("/")[-1]
@ -121,9 +121,9 @@ class GlacierResponse(BaseResponse):
self.setup_class(request, full_url, headers)
return self._vault_jobs_response(request, full_url, headers)
def _vault_jobs_response(
def _vault_jobs_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
method = request.method
if hasattr(request, "body"):
body = request.body

View File

@ -32,8 +32,10 @@ class GlueResponse(BaseResponse):
if "CatalogId" in self.parameters:
database_input["CatalogId"] = self.parameters.get("CatalogId") # type: ignore
self.glue_backend.create_database(
database_name, database_input, self.parameters.get("Tags")
) # type: ignore[arg-type]
database_name,
database_input, # type: ignore[arg-type]
self.parameters.get("Tags"), # type: ignore[arg-type]
)
return ""
def get_database(self) -> str:
@ -136,8 +138,8 @@ class GlueResponse(BaseResponse):
table_name = self.parameters.get("TableName")
expression = self.parameters.get("Expression")
partitions = self.glue_backend.get_partitions(
database_name,
table_name,
database_name, # type: ignore[arg-type]
table_name, # type: ignore[arg-type]
expression, # type: ignore[arg-type]
)
@ -158,8 +160,8 @@ class GlueResponse(BaseResponse):
partitions_to_get = self.parameters.get("PartitionsToGet")
partitions = self.glue_backend.batch_get_partition(
database_name,
table_name,
database_name, # type: ignore[arg-type]
table_name, # type: ignore[arg-type]
partitions_to_get, # type: ignore[arg-type]
)
@ -178,8 +180,8 @@ class GlueResponse(BaseResponse):
table_name = self.parameters.get("TableName")
partition_input = self.parameters.get("PartitionInputList")
errors_output = self.glue_backend.batch_create_partition(
database_name,
table_name,
database_name, # type: ignore[arg-type]
table_name, # type: ignore[arg-type]
partition_input, # type: ignore[arg-type]
)
@ -196,9 +198,9 @@ class GlueResponse(BaseResponse):
part_to_update = self.parameters.get("PartitionValueList")
self.glue_backend.update_partition(
database_name,
table_name,
part_input,
database_name, # type: ignore[arg-type]
table_name, # type: ignore[arg-type]
part_input, # type: ignore[arg-type]
part_to_update, # type: ignore[arg-type]
)
return ""
@ -209,8 +211,8 @@ class GlueResponse(BaseResponse):
entries = self.parameters.get("Entries")
errors_output = self.glue_backend.batch_update_partition(
database_name,
table_name,
database_name, # type: ignore[arg-type]
table_name, # type: ignore[arg-type]
entries, # type: ignore[arg-type]
)
@ -234,8 +236,8 @@ class GlueResponse(BaseResponse):
parts = self.parameters.get("PartitionsToDelete")
errors_output = self.glue_backend.batch_delete_partition(
database_name,
table_name,
database_name, # type: ignore[arg-type]
table_name, # type: ignore[arg-type]
parts, # type: ignore[arg-type]
)

View File

@ -916,9 +916,9 @@ class GreengrassBackend(BaseBackend):
return False
@staticmethod
def _validate_subscription_target_or_source(
def _validate_subscription_target_or_source( # type: ignore[misc]
subscriptions: List[Dict[str, Any]],
) -> None: # type: ignore[misc]
) -> None:
target_errors: List[str] = []
source_errors: List[str] = []

View File

@ -16,9 +16,9 @@ class GreengrassResponse(BaseResponse):
def greengrass_backend(self) -> GreengrassBackend:
return greengrass_backends[self.current_account][self.region]
def core_definitions(
def core_definitions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -45,9 +45,9 @@ class GreengrassResponse(BaseResponse):
)
return 201, {"status": 201}, json.dumps(res.to_dict())
def core_definition(
def core_definition( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -81,9 +81,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps({})
def core_definition_versions(
def core_definition_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -110,9 +110,9 @@ class GreengrassResponse(BaseResponse):
json.dumps({"Versions": [core_def_ver.to_dict() for core_def_ver in res]}),
)
def core_definition_version(
def core_definition_version( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -127,9 +127,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps(res.to_dict(include_detail=True))
def device_definitions(
def device_definitions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -160,9 +160,9 @@ class GreengrassResponse(BaseResponse):
),
)
def device_definition_versions(
def device_definition_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -193,9 +193,9 @@ class GreengrassResponse(BaseResponse):
),
)
def device_definition(
def device_definition( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -229,9 +229,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps({})
def device_definition_version(
def device_definition_version( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -246,9 +246,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps(res.to_dict(include_detail=True))
def resource_definitions(
def resource_definitions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -273,9 +273,9 @@ class GreengrassResponse(BaseResponse):
json.dumps({"Definitions": [i.to_dict() for i in res]}),
)
def resource_definition(
def resource_definition( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -309,9 +309,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps({})
def resource_definition_versions(
def resource_definition_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -343,9 +343,9 @@ class GreengrassResponse(BaseResponse):
),
)
def resource_definition_version(
def resource_definition_version( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -360,9 +360,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps(res.to_dict())
def function_definitions(
def function_definitions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -389,9 +389,9 @@ class GreengrassResponse(BaseResponse):
),
)
def function_definition(
def function_definition( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -425,9 +425,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps({})
def function_definition_versions(
def function_definition_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -456,9 +456,9 @@ class GreengrassResponse(BaseResponse):
versions = [i.to_dict() for i in res.values()]
return 200, {"status": 200}, json.dumps({"Versions": versions})
def function_definition_version(
def function_definition_version( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -473,9 +473,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps(res.to_dict())
def subscription_definitions(
def subscription_definitions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -507,9 +507,9 @@ class GreengrassResponse(BaseResponse):
),
)
def subscription_definition(
def subscription_definition( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -543,9 +543,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps({})
def subscription_definition_versions(
def subscription_definition_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -571,9 +571,9 @@ class GreengrassResponse(BaseResponse):
versions = [i.to_dict() for i in res.values()]
return 200, {"status": 200}, json.dumps({"Versions": versions})
def subscription_definition_version(
def subscription_definition_version( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -643,9 +643,9 @@ class GreengrassResponse(BaseResponse):
self.greengrass_backend.update_group(group_id=group_id, name=name)
return 200, {"status": 200}, json.dumps({})
def group_versions(
def group_versions( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":
@ -742,9 +742,9 @@ class GreengrassResponse(BaseResponse):
json.dumps({"Deployments": deployments}),
)
def deployment_satus(
def deployment_satus( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "GET":
@ -760,9 +760,9 @@ class GreengrassResponse(BaseResponse):
)
return 200, {"status": 200}, json.dumps(res.to_dict())
def deployments_reset(
def deployments_reset( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if self.method == "POST":

View File

@ -463,9 +463,9 @@ class AWSManagedPolicy(ManagedPolicy):
"""AWS-managed policy."""
@classmethod
def from_data(
def from_data( # type: ignore[misc]
cls, name: str, account_id: str, data: Dict[str, Any]
) -> "AWSManagedPolicy": # type: ignore[misc]
) -> "AWSManagedPolicy":
return cls(
name,
account_id=account_id,

View File

@ -222,9 +222,10 @@ class BaseIAMPolicyValidator:
)
@staticmethod
def _validate_string_or_list_of_strings_syntax(
statement: Dict[str, Any], key: str
) -> None: # type: ignore[misc]
def _validate_string_or_list_of_strings_syntax( # type: ignore[misc]
statement: Dict[str, Any],
key: str,
) -> None:
if key in statement:
assert isinstance(statement[key], (str, list))
if isinstance(statement[key], list):
@ -456,9 +457,10 @@ class BaseIAMPolicyValidator:
assert resource[2] != ""
@staticmethod
def _legacy_parse_condition(
condition_key: str, condition_value: Dict[str, Any]
) -> None: # type: ignore[misc]
def _legacy_parse_condition( # type: ignore[misc]
condition_key: str,
condition_value: Dict[str, Any],
) -> None:
stripped_condition_key = IAMPolicyDocumentValidator._strip_condition_key(
condition_key
)

View File

@ -196,7 +196,7 @@ class Inspector2Backend(BaseBackend):
"region": "us-east-1", # This is the default - can be omitted
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/inspector2/findings-results",
"http://motoapi.amazonaws.com/moto-api/static/inspector2/findings-results",
json=findings,
)

View File

@ -44,9 +44,9 @@ class FakeShadow(BaseModel):
)
@classmethod
def create_from_previous_version(
def create_from_previous_version( # type: ignore[misc]
cls, previous_shadow: Optional["FakeShadow"], payload: Optional[Dict[str, Any]]
) -> "FakeShadow": # type: ignore[misc]
) -> "FakeShadow":
"""
set None to payload when you want to delete shadow
"""

View File

@ -734,8 +734,8 @@ class KinesisBackend(BaseBackend):
data = record.get("Data")
sequence_number, shard_id = stream.put_record(
partition_key,
explicit_hash_key,
partition_key, # type: ignore[arg-type]
explicit_hash_key, # type: ignore[arg-type]
data, # type: ignore[arg-type]
)
response["Records"].append(

View File

@ -8,8 +8,8 @@ def get_body_from_form_data(
body: bytes, boundary: str
) -> Tuple[Optional[bytes], Dict[str, str]]:
body_stream = io.BytesIO(body)
parser = multipart.MultipartParser(body_stream, boundary=boundary)
parser = multipart.MultipartParser(boundary=boundary)
parser.write(body_stream)
data = None
headers: Dict[str, str] = {}
for prt in parser.parts():

View File

@ -588,9 +588,9 @@ class OpsWorksBackend(BaseBackend):
raise ResourceNotFoundException(", ".join(unknown_apps))
return [self.apps[id].to_dict() for id in app_ids]
def describe_instances(
def describe_instances( # type: ignore[return]
self, instance_ids: List[str], layer_id: str, stack_id: str
) -> List[Dict[str, Any]]: # type: ignore[return]
) -> List[Dict[str, Any]]:
if len(list(filter(None, (instance_ids, layer_id, stack_id)))) != 1:
raise ValidationException(
"Please provide either one or more "

View File

@ -2754,9 +2754,9 @@ class RDSBackend(BaseBackend):
"InvalidParameterValue", f"Invalid resource name: {arn}"
)
def add_tags_to_resource(
def add_tags_to_resource( # type: ignore[return]
self, arn: str, tags: List[Dict[str, str]]
) -> List[Dict[str, str]]: # type: ignore[return]
) -> List[Dict[str, str]]:
if self.arn_regex.match(arn):
arn_breakdown = arn.split(":")
resource_type = arn_breakdown[-2]
@ -2813,9 +2813,9 @@ class RDSBackend(BaseBackend):
raise InvalidParameterCombination(str(e))
@staticmethod
def _merge_tags(
def _merge_tags( # type: ignore[misc]
old_tags: List[Dict[str, Any]], new_tags: List[Dict[str, Any]]
) -> List[Dict[str, Any]]: # type: ignore[misc]
) -> List[Dict[str, Any]]:
tags_dict = dict()
tags_dict.update({d["Key"]: d["Value"] for d in old_tags})
tags_dict.update({d["Key"]: d["Value"] for d in new_tags})

View File

@ -63,7 +63,7 @@ class RDSDataServiceBackend(BaseBackend):
],
}
resp = requests.post(
"http://motoapi.amazonaws.com:5000/moto-api/static/rds-data/statement-results",
"http://motoapi.amazonaws.com/moto-api/static/rds-data/statement-results",
json=expected_results,
)
assert resp.status_code == 201

View File

@ -23,8 +23,8 @@ from moto.s3.models import S3Backend, s3_backends
from moto.sns.models import SNSBackend, sns_backends
from moto.sqs.models import SQSBackend, sqs_backends
from moto.ssm.models import SimpleSystemManagerBackend, ssm_backends
from moto.workspaces.models import WorkSpacesBackend, workspaces_backends
from moto.utilities.tagging_service import TaggingService
from moto.workspaces.models import WorkSpacesBackend, workspaces_backends
# Left: EC2 ElastiCache RDS ELB CloudFront Lambda EMR Glacier Kinesis Redshift Route53
# StorageGateway DynamoDB MachineLearning ACM DirectConnect DirectoryService CloudHSM

View File

@ -36,9 +36,9 @@ class Route53(BaseResponse):
def backend(self) -> Route53Backend:
return route53_backends[self.current_account]["global"]
def list_or_create_hostzone_response(
def list_or_create_hostzone_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
# Set these here outside the scope of the try/except
@ -136,9 +136,9 @@ class Route53(BaseResponse):
template = Template(GET_HOSTED_ZONE_COUNT_RESPONSE)
return 200, headers, template.render(zone_count=num_zones, xmlns=XMLNS)
def get_or_delete_hostzone_response(
def get_or_delete_hostzone_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
zoneid = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
@ -158,9 +158,9 @@ class Route53(BaseResponse):
template = Template(UPDATE_HOSTED_ZONE_COMMENT_RESPONSE)
return 200, headers, template.render(zone=zone)
def get_dnssec_response(
def get_dnssec_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
# returns static response
# TODO: implement enable/disable dnssec apis
self.setup_class(request, full_url, headers)
@ -212,9 +212,9 @@ class Route53(BaseResponse):
template = Template(DISASSOCIATE_VPC_RESPONSE)
return 200, headers, template.render(comment=comment)
def rrset_response(
def rrset_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
method = request.method
@ -285,9 +285,9 @@ class Route53(BaseResponse):
)
return 200, headers, r_template
def health_check_response1(
def health_check_response1( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
method = request.method
@ -327,9 +327,9 @@ class Route53(BaseResponse):
template.render(health_checks=health_checks, xmlns=XMLNS),
)
def health_check_response2(
def health_check_response2( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
method = request.method
@ -365,9 +365,9 @@ class Route53(BaseResponse):
template = Template(UPDATE_HEALTH_CHECK_RESPONSE)
return 200, headers, template.render(health_check=health_check)
def health_check_status_response(
def health_check_status_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
method = request.method
@ -402,9 +402,9 @@ class Route53(BaseResponse):
f"The action for {action} has not been implemented for route 53"
)
def list_or_change_tags_for_resource_request(
def list_or_change_tags_for_resource_request( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
id_ = self.parsed_url.path.split("/")[-1]
@ -439,9 +439,9 @@ class Route53(BaseResponse):
template = Template(GET_CHANGE_RESPONSE)
return 200, headers, template.render(change_id=change_id, xmlns=XMLNS)
def list_or_create_query_logging_config_response(
def list_or_create_query_logging_config_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "POST":
@ -488,9 +488,9 @@ class Route53(BaseResponse):
),
)
def get_or_delete_query_logging_config_response(
def get_or_delete_query_logging_config_response( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
query_logging_config_id = self.parsed_url.path.rstrip("/").rsplit("/", 1)[1]
@ -509,9 +509,9 @@ class Route53(BaseResponse):
self.backend.delete_query_logging_config(query_logging_config_id)
return 200, headers, ""
def reusable_delegation_sets(
def reusable_delegation_sets( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "GET":
delegation_sets = self.backend.list_reusable_delegation_sets()
@ -541,9 +541,9 @@ class Route53(BaseResponse):
template.render(delegation_set=delegation_set),
)
def reusable_delegation_set(
def reusable_delegation_set( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
ds_id = self.parsed_url.path.rstrip("/").rsplit("/")[-1]
if request.method == "GET":

View File

@ -1,7 +1,9 @@
import base64
import bz2
import codecs
import copy
import datetime
import gzip
import itertools
import json
import os
@ -12,6 +14,7 @@ import threading
import urllib.parse
from bisect import insort
from importlib import reload
from io import BytesIO
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
from moto.cloudwatch.models import MetricDatum
@ -374,13 +377,13 @@ class FakeKey(BaseModel, ManagedState):
now = utcnow()
try:
until = datetime.datetime.strptime(
self.lock_until,
"%Y-%m-%dT%H:%M:%SZ", # type: ignore
self.lock_until, # type: ignore
"%Y-%m-%dT%H:%M:%SZ",
)
except ValueError:
until = datetime.datetime.strptime(
self.lock_until,
"%Y-%m-%dT%H:%M:%S.%fZ", # type: ignore
self.lock_until, # type: ignore
"%Y-%m-%dT%H:%M:%S.%fZ",
)
if until > now:
@ -478,11 +481,11 @@ class FakeMultipart(BaseModel):
raise NoSuchUpload(upload_id=part_id)
key = FakeKey(
part_id,
part_id, # type: ignore
value,
account_id=self.account_id,
encryption=self.sse_encryption,
kms_key_id=self.kms_key_id, # type: ignore
kms_key_id=self.kms_key_id,
)
if part_id in self.parts:
# We're overwriting the current part - dispose of it first
@ -2376,6 +2379,7 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
- 's3:ObjectCreated:Post'
- 's3:ObjectCreated:Put'
- 's3:ObjectDeleted'
- 's3:ObjectRestore:Post'
"""
bucket = self.get_bucket(bucket_name)
bucket.set_notification_configuration(notification_config)
@ -2858,6 +2862,7 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
key_name: str,
select_query: str,
input_details: Dict[str, Any],
output_details: Dict[str, Any],
) -> List[bytes]:
"""
Highly experimental. Please raise an issue if you find any inconsistencies/bugs.
@ -2870,24 +2875,53 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
"""
self.get_bucket(bucket_name)
key = self.get_object(bucket_name, key_name)
query_input = key.value.decode("utf-8") # type: ignore
if key is None:
raise MissingKey(key=key_name)
if input_details.get("CompressionType") == "GZIP":
with gzip.open(BytesIO(key.value), "rt") as f:
query_input = f.read()
elif input_details.get("CompressionType") == "BZIP2":
query_input = bz2.decompress(key.value).decode("utf-8")
else:
query_input = key.value.decode("utf-8")
if "CSV" in input_details:
# input is in CSV - we need to convert it to JSON before parsing
from py_partiql_parser._internal.csv_converter import ( # noqa # pylint: disable=unused-import
csv_to_json,
)
from py_partiql_parser import csv_to_json
use_headers = input_details["CSV"].get("FileHeaderInfo", "") == "USE"
use_headers = (input_details.get("CSV") or {}).get(
"FileHeaderInfo", ""
) == "USE"
query_input = csv_to_json(query_input, use_headers)
query_result = parse_query(query_input, select_query)
from py_partiql_parser import SelectEncoder
query_result = parse_query(query_input, select_query) # type: ignore
return [
json.dumps(x, indent=None, separators=(",", ":"), cls=SelectEncoder).encode(
"utf-8"
)
for x in query_result
]
record_delimiter = "\n"
if "JSON" in output_details:
record_delimiter = (output_details.get("JSON") or {}).get(
"RecordDelimiter"
) or "\n"
elif "CSV" in output_details:
record_delimiter = (output_details.get("CSV") or {}).get(
"RecordDelimiter"
) or "\n"
if "CSV" in output_details:
field_delim = (output_details.get("CSV") or {}).get("FieldDelimiter") or ","
from py_partiql_parser import json_to_csv
query_result = json_to_csv(query_result, field_delim, record_delimiter)
return [query_result.encode("utf-8")] # type: ignore
else:
from py_partiql_parser import SelectEncoder
return [
(
json.dumps(x, indent=None, separators=(",", ":"), cls=SelectEncoder)
+ record_delimiter
).encode("utf-8")
for x in query_result
]
def restore_object(
self, bucket_name: str, key_name: str, days: Optional[str], type_: Optional[str]

View File

@ -2291,9 +2291,9 @@ class S3Response(BaseResponse):
input_details = request["InputSerialization"]
output_details = request["OutputSerialization"]
results = self.backend.select_object_content(
bucket_name, key_name, select_query, input_details
bucket_name, key_name, select_query, input_details, output_details
)
return 200, {}, serialize_select(results, output_details)
return 200, {}, serialize_select(results)
else:
raise NotImplementedError(

View File

@ -49,11 +49,8 @@ def _create_end_message() -> bytes:
return _create_message(content_type=None, event_type=b"End", payload=b"")
def serialize_select(data_list: List[bytes], output_details: Dict[str, Any]) -> bytes:
delimiter = (
(output_details.get("JSON") or {}).get("RecordDelimiter") or "\n"
).encode("utf-8")
def serialize_select(data_list: List[bytes]) -> bytes:
response = b""
for data in data_list:
response += _create_data_message(data + delimiter)
response += _create_data_message(data)
return response + _create_stats_message() + _create_end_message()

View File

@ -19,9 +19,9 @@ class S3ControlResponse(BaseResponse):
def backend(self) -> S3ControlBackend:
return s3control_backends[self.current_account]["global"]
def public_access_block(
def public_access_block( # type: ignore
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
try:
if request.method == "GET":
@ -70,9 +70,9 @@ class S3ControlResponse(BaseResponse):
if request.method == "DELETE":
return self.delete_access_point(full_url)
def access_point_policy(
def access_point_policy( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "PUT":
return self.create_access_point_policy(full_url)
@ -81,9 +81,9 @@ class S3ControlResponse(BaseResponse):
if request.method == "DELETE":
return self.delete_access_point_policy(full_url)
def access_point_policy_status(
def access_point_policy_status( # type: ignore[return]
self, request: Any, full_url: str, headers: Any
) -> TYPE_RESPONSE: # type: ignore[return]
) -> TYPE_RESPONSE:
self.setup_class(request, full_url, headers)
if request.method == "PUT":
return self.create_access_point(full_url)

View File

@ -9,7 +9,7 @@ def name_filter(secret: "FakeSecret", names: List[str]) -> bool:
def description_filter(secret: "FakeSecret", descriptions: List[str]) -> bool:
return _matcher(descriptions, [secret.description]) # type: ignore
return _matcher(descriptions, [secret.description], match_prefix=False) # type: ignore
def tag_key(secret: "FakeSecret", tag_keys: List[str]) -> bool:
@ -30,21 +30,31 @@ def filter_all(secret: "FakeSecret", values: List[str]) -> bool:
return _matcher(values, attributes) # type: ignore
def _matcher(patterns: List[str], strings: List[str]) -> bool:
def _matcher(
patterns: List[str], strings: List[str], match_prefix: bool = True
) -> bool:
for pattern in [p for p in patterns if p.startswith("!")]:
for string in strings:
if _match_pattern(pattern[1:], string):
return False
if not _match_pattern(pattern[1:], string, match_prefix):
return True
for pattern in [p for p in patterns if not p.startswith("!")]:
for string in strings:
if _match_pattern(pattern, string):
if _match_pattern(pattern, string, match_prefix):
return True
return False
def _match_pattern(pattern: str, value: str) -> bool:
for word in pattern.split(" "):
if word not in value:
return False
def _match_pattern(pattern: str, value: str, match_prefix: bool = True) -> bool:
if match_prefix:
return value.startswith(pattern)
else:
pattern_words = pattern.split(" ")
value_words = value.split(" ")
for pattern_word in pattern_words:
# all words in value must start with pattern_word
if not any(
value_word.startswith(pattern_word) for value_word in value_words
):
return False
return True

View File

@ -143,9 +143,9 @@ class EmailResponse(BaseResponse):
message = self.backend.send_bulk_templated_email(
source,
template,
template_data,
destinations, # type: ignore
template, # type: ignore
template_data, # type: ignore
destinations,
)
template = self.response_template(SEND_BULK_TEMPLATED_EMAIL_RESPONSE)
result = template.render(message=message)

View File

@ -46,7 +46,7 @@ class EachBlockProcessor(BlockProcessor):
_processor = get_processor(self.tokenizer)(
self.template,
template_data,
template_data, # type: ignore
self.tokenizer, # type: ignore
)
# If we've reached the end, we should stop processing

View File

@ -715,7 +715,7 @@ class SNSBackend(BaseBackend):
try:
del self.platform_endpoints[arn]
except KeyError:
raise SNSNotFoundError(f"Endpoint with arn {arn} not found")
pass # idempotent operation
def get_subscription_attributes(self, arn: str) -> Dict[str, Any]:
subscription = self.subscriptions.get(arn)

View File

@ -1531,15 +1531,6 @@
"Value": "HTTPS"
}
},
"support": {
"Value": "support",
"endpoint": {
"Value": "support.us-east-1.amazonaws.com"
},
"protocols": {
"Value": "HTTPS"
}
},
"swf": {
"Value": "swf",
"endpoint": {
@ -9380,6 +9371,15 @@
"Value": "HTTPS"
}
},
"neptune": {
"Value": "neptune",
"endpoint": {
"Value": "rds.ap-northeast-3.amazonaws.com"
},
"protocols": {
"Value": "HTTPS, HTTP"
}
},
"network-firewall": {
"Value": "network-firewall",
"endpoint": {
@ -57883,6 +57883,24 @@
"Value": "HTTPS"
}
},
"lex-runtime": {
"Value": "lex-runtime",
"endpoint": {
"Value": "runtime-v2-lex.us-gov-west-1.amazonaws.com"
},
"protocols": {
"Value": "HTTPS"
}
},
"lexv2-models": {
"Value": "lexv2-models",
"endpoint": {
"Value": "models-v2-lex.us-gov-west-1.amazonaws.com"
},
"protocols": {
"Value": "HTTPS"
}
},
"license-manager": {
"Value": "license-manager",
"endpoint": {

View File

@ -36832,6 +36832,15 @@
"Value": "HTTPS"
}
},
"us-gov-west-1": {
"Value": "us-gov-west-1",
"endpoint": {
"Value": "runtime-v2-lex.us-gov-west-1.amazonaws.com"
},
"protocols": {
"Value": "HTTPS"
}
},
"us-west-2": {
"Value": "us-west-2",
"endpoint": {
@ -36939,6 +36948,15 @@
"Value": "HTTPS"
}
},
"us-gov-west-1": {
"Value": "us-gov-west-1",
"endpoint": {
"Value": "models-v2-lex.us-gov-west-1.amazonaws.com"
},
"protocols": {
"Value": "HTTPS"
}
},
"us-west-2": {
"Value": "us-west-2",
"endpoint": {
@ -42676,6 +42694,15 @@
"Value": "HTTPS, HTTP"
}
},
"ap-northeast-3": {
"Value": "ap-northeast-3",
"endpoint": {
"Value": "rds.ap-northeast-3.amazonaws.com"
},
"protocols": {
"Value": "HTTPS, HTTP"
}
},
"ap-south-1": {
"Value": "ap-south-1",
"endpoint": {
@ -60961,15 +60988,6 @@
"Value": "https://aws.amazon.com/premiumsupport/"
},
"regions": {
"af-south-1": {
"Value": "af-south-1",
"endpoint": {
"Value": "support.us-east-1.amazonaws.com"
},
"protocols": {
"Value": "HTTPS"
}
},
"ap-east-1": {
"Value": "ap-east-1",
"endpoint": {

View File

@ -106,9 +106,9 @@ class StepFunctionsParserBackend(StepFunctionBackend):
raise InvalidToken()
def send_task_success(
self, task_token: TaskToken, output: SensitiveData
self, task_token: TaskToken, outcome: SensitiveData
) -> SendTaskSuccessOutput:
outcome = CallbackOutcomeSuccess(callback_id=task_token, output=output)
outcome = CallbackOutcomeSuccess(callback_id=task_token, output=outcome)
running_executions = self._get_executions(ExecutionStatus.RUNNING)
for execution in running_executions:
try:

View File

@ -1,7 +1,7 @@
import datetime
import re
from base64 import b64decode
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, List, Optional, Tuple
import xmltodict

View File

@ -61,9 +61,9 @@ class Domain(BaseModel):
"configuration": {"workflowExecutionRetentionPeriodInDays": self.retention},
}
def get_type(
def get_type( # type: ignore
self, kind: str, name: str, version: str, ignore_empty: bool = False
) -> "GenericType": # type: ignore
) -> "GenericType":
try:
return self.types[kind][name][version]
except KeyError:

View File

@ -54,7 +54,7 @@ all =
openapi-spec-validator>=0.5.0
pyparsing>=3.0.7
jsondiff>=1.1.2
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
aws-xray-sdk!=0.96,>=0.93
setuptools
multipart
@ -69,7 +69,7 @@ proxy =
openapi-spec-validator>=0.5.0
pyparsing>=3.0.7
jsondiff>=1.1.2
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
aws-xray-sdk!=0.96,>=0.93
setuptools
multipart
@ -84,7 +84,7 @@ server =
openapi-spec-validator>=0.5.0
pyparsing>=3.0.7
jsondiff>=1.1.2
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
aws-xray-sdk!=0.96,>=0.93
setuptools
flask!=2.2.0,!=2.2.1
@ -119,7 +119,7 @@ cloudformation =
openapi-spec-validator>=0.5.0
pyparsing>=3.0.7
jsondiff>=1.1.2
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
aws-xray-sdk!=0.96,>=0.93
setuptools
cloudfront =
@ -141,10 +141,10 @@ dms =
ds =
dynamodb =
docker>=3.0.0
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
dynamodbstreams =
docker>=3.0.0
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
ebs =
ec2 =
ec2instanceconnect =
@ -208,15 +208,15 @@ resourcegroupstaggingapi =
openapi-spec-validator>=0.5.0
pyparsing>=3.0.7
jsondiff>=1.1.2
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
route53 =
route53resolver =
s3 =
PyYAML>=5.1
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
s3crc32c =
PyYAML>=5.1
py-partiql-parser==0.5.1
py-partiql-parser==0.5.2
crc32c
s3control =
sagemaker =

View File

@ -41,6 +41,13 @@ def test_run_function_no_log():
# Execute
result = client.invoke(FunctionName=FUNCTION_NAME, Payload=json.dumps(payload))
# Verify
assert result["StatusCode"] == 200
assert json.loads(result["Payload"].read().decode("utf-8")) == payload
# Execute
result = client.invoke(FunctionName=FUNCTION_NAME)
# Verify
assert result["StatusCode"] == 200
assert result["Payload"].read().decode("utf-8") == "Simple Lambda happy path OK"

View File

@ -85,9 +85,9 @@ def test_decorater_wrapped_gets_set() -> None:
Moto decorator's __wrapped__ should get set to the tests function
"""
assert (
test_decorater_wrapped_gets_set.__wrapped__.__name__
test_decorater_wrapped_gets_set.__wrapped__.__name__ # type: ignore
== "test_decorater_wrapped_gets_set"
) # type: ignore
)
@mock_aws

View File

@ -143,8 +143,8 @@ def test_create_export_task_happy_path(logs, s3, log_group_name, bucket_name):
@pytest.mark.aws_verified
def test_create_export_task_raises_ClientError_when_bucket_not_found(
logs,
log_group_name, # pylint: disable=redefined-outer-name
):
log_group_name,
): # pylint: disable=redefined-outer-name
destination = "368a7022dea3dd621"
fromTime = 1611316574
to = 1642852574
@ -166,8 +166,8 @@ def test_create_export_task_raises_ClientError_when_bucket_not_found(
@pytest.mark.aws_verified
def test_create_export_raises_ResourceNotFoundException_log_group_not_found(
logs,
bucket_name, # pylint: disable=redefined-outer-name
):
bucket_name,
): # pylint: disable=redefined-outer-name
with pytest.raises(logs.exceptions.ResourceNotFoundException) as exc:
logs.create_export_task(
logGroupName=f"/aws/nonexisting/{str(uuid4())[0:6]}",

View File

@ -886,8 +886,6 @@ def test_get_resources_sns():
@mock_aws
def test_get_resources_ssm():
import json
import yaml
from tests.test_ssm.test_ssm_docs import _get_yaml_template

View File

@ -1,7 +1,10 @@
import bz2
import gzip
import json
import boto3
import pytest
from botocore.exceptions import ClientError
from . import s3_aws_verified
@ -45,6 +48,24 @@ def create_test_files(bucket_name):
Key="nested.json",
Body=json.dumps(NESTED_JSON),
)
client.put_object(
Bucket=bucket_name,
Key="json.gzip",
Body=gzip.compress(json.dumps(NESTED_JSON).encode("utf-8")),
)
client.put_object(
Bucket=bucket_name,
Key="json.bz2",
Body=bz2.compress(json.dumps(NESTED_JSON).encode("utf-8")),
)
client.put_object(
Bucket=bucket_name,
Key="csv.gzip",
Body=gzip.compress(SIMPLE_CSV.encode("utf-8")),
)
client.put_object(
Bucket=bucket_name, Key="csv.bz2", Body=bz2.compress(SIMPLE_CSV.encode("utf-8"))
)
@pytest.mark.aws_verified
@ -226,3 +247,113 @@ def test_nested_json__select_all(bucket_name=None):
assert records[-1] == ","
assert json.loads(records[:-1]) == NESTED_JSON
@pytest.mark.aws_verified
@s3_aws_verified
def test_gzipped_json(bucket_name=None):
client = boto3.client("s3", "us-east-1")
create_test_files(bucket_name)
content = client.select_object_content(
Bucket=bucket_name,
Key="json.gzip",
Expression="SELECT count(*) FROM S3Object",
ExpressionType="SQL",
InputSerialization={"JSON": {"Type": "DOCUMENT"}, "CompressionType": "GZIP"},
OutputSerialization={"JSON": {"RecordDelimiter": ","}},
)
result = list(content["Payload"])
assert {"Records": {"Payload": b'{"_1":1},'}} in result
@pytest.mark.aws_verified
@s3_aws_verified
def test_bzipped_json(bucket_name=None):
client = boto3.client("s3", "us-east-1")
create_test_files(bucket_name)
content = client.select_object_content(
Bucket=bucket_name,
Key="json.bz2",
Expression="SELECT count(*) FROM S3Object",
ExpressionType="SQL",
InputSerialization={"JSON": {"Type": "DOCUMENT"}, "CompressionType": "BZIP2"},
OutputSerialization={"JSON": {"RecordDelimiter": ","}},
)
result = list(content["Payload"])
assert {"Records": {"Payload": b'{"_1":1},'}} in result
@pytest.mark.aws_verified
@s3_aws_verified
def test_bzipped_csv_to_csv(bucket_name=None):
client = boto3.client("s3", "us-east-1")
create_test_files(bucket_name)
# Count Records
content = client.select_object_content(
Bucket=bucket_name,
Key="csv.bz2",
Expression="SELECT count(*) FROM S3Object",
ExpressionType="SQL",
InputSerialization={"CSV": {}, "CompressionType": "BZIP2"},
OutputSerialization={"CSV": {"RecordDelimiter": "_", "FieldDelimiter": ":"}},
)
result = list(content["Payload"])
assert {"Records": {"Payload": b"4_"}} in result
# Count Records
content = client.select_object_content(
Bucket=bucket_name,
Key="csv.bz2",
Expression="SELECT count(*) FROM S3Object",
ExpressionType="SQL",
InputSerialization={"CSV": {}, "CompressionType": "BZIP2"},
OutputSerialization={"CSV": {}},
)
result = list(content["Payload"])
assert {"Records": {"Payload": b"4\n"}} in result
# Mirror records
content = client.select_object_content(
Bucket=bucket_name,
Key="csv.bz2",
Expression="SELECT * FROM S3Object",
ExpressionType="SQL",
InputSerialization={"CSV": {}, "CompressionType": "BZIP2"},
OutputSerialization={"CSV": {}},
)
result = list(content["Payload"])
assert {"Records": {"Payload": b"a,b,c\ne,r,f\ny,u,i\nq,w,y\n"}} in result
# Mirror records, specifying output format
content = client.select_object_content(
Bucket=bucket_name,
Key="csv.bz2",
Expression="SELECT * FROM S3Object",
ExpressionType="SQL",
InputSerialization={"CSV": {}, "CompressionType": "BZIP2"},
OutputSerialization={"CSV": {"RecordDelimiter": "\n", "FieldDelimiter": ":"}},
)
result = list(content["Payload"])
assert {"Records": {"Payload": b"a:b:c\ne:r:f\ny:u:i\nq:w:y\n"}} in result
@pytest.mark.aws_verified
@s3_aws_verified
def test_select_unknown_key(bucket_name=None):
client = boto3.client("s3", "us-east-1")
with pytest.raises(ClientError) as exc:
client.select_object_content(
Bucket=bucket_name,
Key="unknown",
Expression="SELECT count(*) FROM S3Object",
ExpressionType="SQL",
InputSerialization={"CSV": {}, "CompressionType": "BZIP2"},
OutputSerialization={
"CSV": {"RecordDelimiter": "\n", "FieldDelimiter": ":"}
},
)
err = exc.value.response["Error"]
assert err["Code"] == "NoSuchKey"
assert err["Message"] == "The specified key does not exist."
assert err["Key"] == "unknown"

View File

@ -268,7 +268,13 @@ def test_with_filter_with_negation():
)
secret_names = list(map(lambda s: s["Name"], secrets["SecretList"]))
assert secret_names == ["baz"]
for secret_name in ["foo", "bar", "baz"]:
assert secret_name in secret_names
secrets = conn.list_secrets(Filters=[{"Key": "description", "Values": ["!o"]}])
secret_names = list(map(lambda s: s["Name"], secrets["SecretList"]))
for secret_name in ["qux", "none"]:
assert secret_name in secret_names
@mock_aws

View File

@ -266,6 +266,13 @@ def test_get_list_endpoints_by_platform_application(api_key=None):
assert len(endpoint_list) == 1
assert endpoint_list[0]["Attributes"]["CustomUserData"] == "some data"
assert endpoint_list[0]["EndpointArn"] == endpoint_arn
resp = conn.delete_endpoint(EndpointArn=endpoint_arn)
assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
# Idempotent operation
resp = conn.delete_endpoint(EndpointArn=endpoint_arn)
assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200
finally:
if application_arn is not None:
conn.delete_platform_application(PlatformApplicationArn=application_arn)