Repository: getmoto/moto
Pull request #6710 (2023-08-21 20:42:59)

Prep release 4.1.15


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0e3fef994..57dd4c7ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,57 @@
 Moto Changelog
 ==============
 
+4.1.15
+------
+Docker Digest for 4.1.15: <autopopulateddigest>
+
+    New Services:
+        * APIGateway Management API:
+            * delete_connection()
+            * get_connection()
+            * post_to_connection()
+
+    New Methods:
+        * Autoscaling:
+            * delete_warm_pool()
+            * describe_warm_pool()
+            * put_warm_pool()
+
+        * IdentityStore:
+            * list_groups()
+
+        * IOTData:
+            * list_named_shadows_for_thing()
+
+        * MQ:
+            * list_tags()
+
+        * ServiceDiscovery:
+            * update_private_dns_namespace()
+            * update_public_dns_namespace()
+
+    Miscellaneous:
+        * ACM-PCA: create_certificate_authority() now supports the KeyStorageSecurityStandard-parameter
+        * Batch: create_job_definition() now returns more default fields in the containerProperties field
+        * CloudTrail: get_trail_status now works with MultiRegion trails
+        * CognitoIDP: UserPools now return the AccountRecoverySetting-field by default
+        * DynamoDB: query() now throws an exception when providing empty ProjectionExpressions/FilterExpressions
+        * EC2: describe_vpc_endpoint_services() now returns user-defined ones
+        * EC2: run_instances() now supports the Monitoring-attribute
+        * IOT: group names can now contain special characters
+        * IOTData now supports named shadows
+        * KMS: create_key() now supports KeySpecs RSA_3072 and RSA_4096
+        * KMS: create_key() now validates the provided KeySpec
+        * Lambda: create_function() now fails when creating a function with a name that already exists
+        * Lambda: put_function_concurrency() now errors on a quota that exceeds the value set by MOTO_LAMBDA_CONCURRENCY_QUOTA
+        * Lambda: update_function_code() now supports the ImageUri-attribute
+        * RDS: describe_db_cluster_snapshots() now returns the TagList-property
+        * ResourceTaggingsAPI: Now supports Glue resources
+        * S3: Authentication now allows specific resources in the IAM access policy 
+        * S3: select_object_content() now takes RecordDelimiter into account
+        * Scheduler: create_scheduler() now errors on duplicate names
+        * TimestreamWrite: create_table() now supports the Schema-attribute
+
 
 4.1.14
 ------
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index c787190af..38a173450 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -499,7 +499,7 @@
 
 ## autoscaling
 <details>
-<summary>52% implemented</summary>
+<summary>56% implemented</summary>
 
 - [X] attach_instances
 - [X] attach_load_balancer_target_groups
@@ -519,7 +519,7 @@
 - [X] delete_policy
 - [X] delete_scheduled_action
 - [X] delete_tags
-- [ ] delete_warm_pool
+- [X] delete_warm_pool
 - [ ] describe_account_limits
 - [ ] describe_adjustment_types
 - [X] describe_auto_scaling_groups
@@ -540,7 +540,7 @@
 - [X] describe_tags
 - [ ] describe_termination_policy_types
 - [ ] describe_traffic_sources
-- [ ] describe_warm_pool
+- [X] describe_warm_pool
 - [X] detach_instances
 - [X] detach_load_balancer_target_groups
 - [X] detach_load_balancers
@@ -555,7 +555,7 @@
 - [ ] put_notification_configuration
 - [X] put_scaling_policy
 - [X] put_scheduled_update_group_action
-- [ ] put_warm_pool
+- [X] put_warm_pool
 - [ ] record_lifecycle_action_heartbeat
 - [X] resume_processes
 - [ ] rollback_instance_refresh
@@ -1055,6 +1055,7 @@
 - [ ] list_approval_rule_templates
 - [ ] list_associated_approval_rule_templates_for_repository
 - [ ] list_branches
+- [ ] list_file_commit_history
 - [ ] list_pull_requests
 - [ ] list_repositories
 - [ ] list_repositories_for_approval_rule_template
@@ -1165,7 +1166,7 @@
 
 ## cognito-idp
 <details>
-<summary>58% implemented</summary>
+<summary>57% implemented</summary>
 
 - [X] add_custom_attributes
 - [X] admin_add_user_to_group
@@ -1227,6 +1228,7 @@
 - [ ] get_device
 - [X] get_group
 - [ ] get_identity_provider_by_identifier
+- [ ] get_log_delivery_configuration
 - [ ] get_signing_certificate
 - [ ] get_ui_customization
 - [X] get_user
@@ -1247,6 +1249,7 @@
 - [ ] resend_confirmation_code
 - [X] respond_to_auth_challenge
 - [ ] revoke_token
+- [ ] set_log_delivery_configuration
 - [ ] set_risk_configuration
 - [ ] set_ui_customization
 - [X] set_user_mfa_preference
@@ -1628,25 +1631,31 @@
 
 ## dms
 <details>
-<summary>6% implemented</summary>
+<summary>4% implemented</summary>
 
 - [ ] add_tags_to_resource
 - [ ] apply_pending_maintenance_action
 - [ ] batch_start_recommendations
 - [ ] cancel_replication_task_assessment_run
+- [ ] create_data_provider
 - [ ] create_endpoint
 - [ ] create_event_subscription
 - [ ] create_fleet_advisor_collector
+- [ ] create_instance_profile
+- [ ] create_migration_project
 - [ ] create_replication_config
 - [ ] create_replication_instance
 - [ ] create_replication_subnet_group
 - [X] create_replication_task
 - [ ] delete_certificate
 - [ ] delete_connection
+- [ ] delete_data_provider
 - [ ] delete_endpoint
 - [ ] delete_event_subscription
 - [ ] delete_fleet_advisor_collector
 - [ ] delete_fleet_advisor_databases
+- [ ] delete_instance_profile
+- [ ] delete_migration_project
 - [ ] delete_replication_config
 - [ ] delete_replication_instance
 - [ ] delete_replication_subnet_group
@@ -1656,17 +1665,28 @@
 - [ ] describe_applicable_individual_assessments
 - [ ] describe_certificates
 - [ ] describe_connections
+- [ ] describe_conversion_configuration
+- [ ] describe_data_providers
 - [ ] describe_endpoint_settings
 - [ ] describe_endpoint_types
 - [ ] describe_endpoints
+- [ ] describe_engine_versions
 - [ ] describe_event_categories
 - [ ] describe_event_subscriptions
 - [ ] describe_events
+- [ ] describe_extension_pack_associations
 - [ ] describe_fleet_advisor_collectors
 - [ ] describe_fleet_advisor_databases
 - [ ] describe_fleet_advisor_lsa_analysis
 - [ ] describe_fleet_advisor_schema_object_summary
 - [ ] describe_fleet_advisor_schemas
+- [ ] describe_instance_profiles
+- [ ] describe_metadata_model_assessments
+- [ ] describe_metadata_model_conversions
+- [ ] describe_metadata_model_exports_as_script
+- [ ] describe_metadata_model_exports_to_target
+- [ ] describe_metadata_model_imports
+- [ ] describe_migration_projects
 - [ ] describe_orderable_replication_instances
 - [ ] describe_pending_maintenance_actions
 - [ ] describe_recommendation_limitations
@@ -1684,10 +1704,15 @@
 - [ ] describe_replications
 - [ ] describe_schemas
 - [ ] describe_table_statistics
+- [ ] export_metadata_model_assessment
 - [ ] import_certificate
 - [ ] list_tags_for_resource
+- [ ] modify_conversion_configuration
+- [ ] modify_data_provider
 - [ ] modify_endpoint
 - [ ] modify_event_subscription
+- [ ] modify_instance_profile
+- [ ] modify_migration_project
 - [ ] modify_replication_config
 - [ ] modify_replication_instance
 - [ ] modify_replication_subnet_group
@@ -1699,6 +1724,12 @@
 - [ ] reload_tables
 - [ ] remove_tags_from_resource
 - [ ] run_fleet_advisor_lsa_analysis
+- [ ] start_extension_pack_association
+- [ ] start_metadata_model_assessment
+- [ ] start_metadata_model_conversion
+- [ ] start_metadata_model_export_as_script
+- [ ] start_metadata_model_export_to_target
+- [ ] start_metadata_model_import
 - [ ] start_recommendations
 - [ ] start_replication
 - [X] start_replication_task
@@ -2727,6 +2758,7 @@
 - [ ] revoke_cache_security_group_ingress
 - [ ] start_migration
 - [ ] test_failover
+- [ ] test_migration
 </details>
 
 ## elasticbeanstalk
@@ -3778,7 +3810,7 @@
 
 ## identitystore
 <details>
-<summary>47% implemented</summary>
+<summary>52% implemented</summary>
 
 - [X] create_group
 - [X] create_group_membership
@@ -3795,7 +3827,7 @@
 - [ ] is_member_in_groups
 - [X] list_group_memberships
 - [ ] list_group_memberships_for_member
-- [ ] list_groups
+- [X] list_groups
 - [ ] list_users
 - [ ] update_group
 - [ ] update_user
@@ -4059,12 +4091,12 @@
 
 ## iot-data
 <details>
-<summary>57% implemented</summary>
+<summary>71% implemented</summary>
 
 - [X] delete_thing_shadow
 - [ ] get_retained_message
 - [X] get_thing_shadow
-- [ ] list_named_shadows_for_thing
+- [X] list_named_shadows_for_thing
 - [ ] list_retained_messages
 - [X] publish
 - [X] update_thing_shadow
@@ -5351,7 +5383,7 @@
 
 ## rds
 <details>
-<summary>39% implemented</summary>
+<summary>38% implemented</summary>
 
 - [ ] add_role_to_db_cluster
 - [ ] add_role_to_db_instance
@@ -5386,6 +5418,7 @@
 - [ ] delete_blue_green_deployment
 - [ ] delete_custom_db_engine_version
 - [X] delete_db_cluster
+- [ ] delete_db_cluster_automated_backup
 - [ ] delete_db_cluster_endpoint
 - [X] delete_db_cluster_parameter_group
 - [X] delete_db_cluster_snapshot
@@ -5404,6 +5437,7 @@
 - [ ] describe_account_attributes
 - [ ] describe_blue_green_deployments
 - [ ] describe_certificates
+- [ ] describe_db_cluster_automated_backups
 - [ ] describe_db_cluster_backtracks
 - [ ] describe_db_cluster_endpoints
 - [X] describe_db_cluster_parameter_groups
@@ -5493,6 +5527,7 @@
 - [X] stop_db_instance
 - [ ] stop_db_instance_automated_backups_replication
 - [ ] switchover_blue_green_deployment
+- [ ] switchover_global_cluster
 - [ ] switchover_read_replica
 </details>
 
@@ -6265,6 +6300,7 @@
 - [ ] get_lineage_group_policy
 - [ ] get_model_package_group_policy
 - [ ] get_sagemaker_servicecatalog_portfolio_status
+- [ ] get_scaling_configuration_recommendation
 - [ ] get_search_suggestions
 - [ ] import_hub_content
 - [ ] list_actions
@@ -6490,7 +6526,7 @@
 
 ## servicediscovery
 <details>
-<summary>61% implemented</summary>
+<summary>69% implemented</summary>
 
 - [X] create_http_namespace
 - [X] create_private_dns_namespace
@@ -6515,8 +6551,8 @@
 - [X] untag_resource
 - [ ] update_http_namespace
 - [ ] update_instance_custom_health_status
-- [ ] update_private_dns_namespace
-- [ ] update_public_dns_namespace
+- [X] update_private_dns_namespace
+- [X] update_public_dns_namespace
 - [X] update_service
 </details>
 
diff --git a/docs/docs/services/athena.rst b/docs/docs/services/athena.rst
index 88970995b..5f788c7a2 100644
--- a/docs/docs/services/athena.rst
+++ b/docs/docs/services/athena.rst
@@ -92,7 +92,7 @@ athena
                 "http://motoapi.amazonaws.com:5000/moto-api/static/athena/query-results",
                 json=expected_results,
             )
-            resp.status_code.should.equal(201)
+            assert resp.status_code == 201
 
             client = boto3.client("athena", region_name="us-east-1")
             details = client.get_query_execution(QueryExecutionId="any_id")["QueryExecution"]
diff --git a/docs/docs/services/autoscaling.rst b/docs/docs/services/autoscaling.rst
index c4a350097..b6e4fb294 100644
--- a/docs/docs/services/autoscaling.rst
+++ b/docs/docs/services/autoscaling.rst
@@ -43,7 +43,7 @@ autoscaling
 - [X] delete_policy
 - [X] delete_scheduled_action
 - [X] delete_tags
-- [ ] delete_warm_pool
+- [X] delete_warm_pool
 - [ ] describe_account_limits
 - [ ] describe_adjustment_types
 - [X] describe_auto_scaling_groups
@@ -69,7 +69,11 @@ autoscaling
 
 - [ ] describe_termination_policy_types
 - [ ] describe_traffic_sources
-- [ ] describe_warm_pool
+- [X] describe_warm_pool
+  
+        Pagination is not yet implemented. Does not create/return any Instances currently.
+        
+
 - [X] detach_instances
 - [X] detach_load_balancer_target_groups
 - [X] detach_load_balancers
@@ -84,7 +88,7 @@ autoscaling
 - [ ] put_notification_configuration
 - [X] put_scaling_policy
 - [X] put_scheduled_update_group_action
-- [ ] put_warm_pool
+- [X] put_warm_pool
 - [ ] record_lifecycle_action_heartbeat
 - [X] resume_processes
 - [ ] rollback_instance_refresh
diff --git a/docs/docs/services/codecommit.rst b/docs/docs/services/codecommit.rst
index 66e0ea059..47368ba04 100644
--- a/docs/docs/services/codecommit.rst
+++ b/docs/docs/services/codecommit.rst
@@ -70,6 +70,7 @@ codecommit
 - [ ] list_approval_rule_templates
 - [ ] list_associated_approval_rule_templates_for_repository
 - [ ] list_branches
+- [ ] list_file_commit_history
 - [ ] list_pull_requests
 - [ ] list_repositories
 - [ ] list_repositories_for_approval_rule_template
diff --git a/docs/docs/services/cognito-idp.rst b/docs/docs/services/cognito-idp.rst
index 6e63a2af8..3f1c67deb 100644
--- a/docs/docs/services/cognito-idp.rst
+++ b/docs/docs/services/cognito-idp.rst
@@ -97,6 +97,7 @@ cognito-idp
 - [ ] get_device
 - [X] get_group
 - [ ] get_identity_provider_by_identifier
+- [ ] get_log_delivery_configuration
 - [ ] get_signing_certificate
 - [ ] get_ui_customization
 - [X] get_user
@@ -117,6 +118,7 @@ cognito-idp
 - [ ] resend_confirmation_code
 - [X] respond_to_auth_challenge
 - [ ] revoke_token
+- [ ] set_log_delivery_configuration
 - [ ] set_risk_configuration
 - [ ] set_ui_customization
 - [X] set_user_mfa_preference
diff --git a/docs/docs/services/dms.rst b/docs/docs/services/dms.rst
index 26d381499..1386f093a 100644
--- a/docs/docs/services/dms.rst
+++ b/docs/docs/services/dms.rst
@@ -29,9 +29,12 @@ dms
 - [ ] apply_pending_maintenance_action
 - [ ] batch_start_recommendations
 - [ ] cancel_replication_task_assessment_run
+- [ ] create_data_provider
 - [ ] create_endpoint
 - [ ] create_event_subscription
 - [ ] create_fleet_advisor_collector
+- [ ] create_instance_profile
+- [ ] create_migration_project
 - [ ] create_replication_config
 - [ ] create_replication_instance
 - [ ] create_replication_subnet_group
@@ -43,10 +46,13 @@ dms
 
 - [ ] delete_certificate
 - [ ] delete_connection
+- [ ] delete_data_provider
 - [ ] delete_endpoint
 - [ ] delete_event_subscription
 - [ ] delete_fleet_advisor_collector
 - [ ] delete_fleet_advisor_databases
+- [ ] delete_instance_profile
+- [ ] delete_migration_project
 - [ ] delete_replication_config
 - [ ] delete_replication_instance
 - [ ] delete_replication_subnet_group
@@ -56,17 +62,28 @@ dms
 - [ ] describe_applicable_individual_assessments
 - [ ] describe_certificates
 - [ ] describe_connections
+- [ ] describe_conversion_configuration
+- [ ] describe_data_providers
 - [ ] describe_endpoint_settings
 - [ ] describe_endpoint_types
 - [ ] describe_endpoints
+- [ ] describe_engine_versions
 - [ ] describe_event_categories
 - [ ] describe_event_subscriptions
 - [ ] describe_events
+- [ ] describe_extension_pack_associations
 - [ ] describe_fleet_advisor_collectors
 - [ ] describe_fleet_advisor_databases
 - [ ] describe_fleet_advisor_lsa_analysis
 - [ ] describe_fleet_advisor_schema_object_summary
 - [ ] describe_fleet_advisor_schemas
+- [ ] describe_instance_profiles
+- [ ] describe_metadata_model_assessments
+- [ ] describe_metadata_model_conversions
+- [ ] describe_metadata_model_exports_as_script
+- [ ] describe_metadata_model_exports_to_target
+- [ ] describe_metadata_model_imports
+- [ ] describe_migration_projects
 - [ ] describe_orderable_replication_instances
 - [ ] describe_pending_maintenance_actions
 - [ ] describe_recommendation_limitations
@@ -88,10 +105,15 @@ dms
 - [ ] describe_replications
 - [ ] describe_schemas
 - [ ] describe_table_statistics
+- [ ] export_metadata_model_assessment
 - [ ] import_certificate
 - [ ] list_tags_for_resource
+- [ ] modify_conversion_configuration
+- [ ] modify_data_provider
 - [ ] modify_endpoint
 - [ ] modify_event_subscription
+- [ ] modify_instance_profile
+- [ ] modify_migration_project
 - [ ] modify_replication_config
 - [ ] modify_replication_instance
 - [ ] modify_replication_subnet_group
@@ -103,6 +125,12 @@ dms
 - [ ] reload_tables
 - [ ] remove_tags_from_resource
 - [ ] run_fleet_advisor_lsa_analysis
+- [ ] start_extension_pack_association
+- [ ] start_metadata_model_assessment
+- [ ] start_metadata_model_conversion
+- [ ] start_metadata_model_export_as_script
+- [ ] start_metadata_model_export_to_target
+- [ ] start_metadata_model_import
 - [ ] start_recommendations
 - [ ] start_replication
 - [X] start_replication_task
diff --git a/docs/docs/services/elasticache.rst b/docs/docs/services/elasticache.rst
index c630cf37d..8ede5a9f9 100644
--- a/docs/docs/services/elasticache.rst
+++ b/docs/docs/services/elasticache.rst
@@ -97,4 +97,5 @@ elasticache
 - [ ] revoke_cache_security_group_ingress
 - [ ] start_migration
 - [ ] test_failover
+- [ ] test_migration
 
diff --git a/docs/docs/services/identitystore.rst b/docs/docs/services/identitystore.rst
index b76932ef0..9125cb0f1 100644
--- a/docs/docs/services/identitystore.rst
+++ b/docs/docs/services/identitystore.rst
@@ -42,7 +42,7 @@ identitystore
 - [ ] is_member_in_groups
 - [X] list_group_memberships
 - [ ] list_group_memberships_for_member
-- [ ] list_groups
+- [X] list_groups
 - [ ] list_users
 - [ ] update_group
 - [ ] update_user
diff --git a/docs/docs/services/iot-data.rst b/docs/docs/services/iot-data.rst
index 146a5d599..09506a20a 100644
--- a/docs/docs/services/iot-data.rst
+++ b/docs/docs/services/iot-data.rst
@@ -28,7 +28,7 @@ iot-data
 - [X] delete_thing_shadow
 - [ ] get_retained_message
 - [X] get_thing_shadow
-- [ ] list_named_shadows_for_thing
+- [X] list_named_shadows_for_thing
 - [ ] list_retained_messages
 - [X] publish
 - [X] update_thing_shadow
diff --git a/docs/docs/services/lambda.rst b/docs/docs/services/lambda.rst
index 19242c216..cfd8bb308 100644
--- a/docs/docs/services/lambda.rst
+++ b/docs/docs/services/lambda.rst
@@ -100,6 +100,19 @@ lambda
 - [ ] publish_version
 - [ ] put_function_code_signing_config
 - [X] put_function_concurrency
+  Establish concurrency limit/reservations for a function
+
+        Actual lambda restricts concurrency to 1000 (default) per region/account
+        across all functions; we approximate that behavior by summing across all
+        functions (hopefully all in the same account and region) and allowing the
+        caller to simulate an increased quota.
+
+        By default, no quota is enforced in order to preserve compatibility with
+        existing code that assumes it can do as many things as it likes. To model
+        actual AWS behavior, define the MOTO_LAMBDA_CONCURRENCY_QUOTA environment
+        variable prior to testing.
+        
+
 - [ ] put_function_event_invoke_config
 - [ ] put_provisioned_concurrency_config
 - [ ] put_runtime_management_config
diff --git a/docs/docs/services/rds-data.rst b/docs/docs/services/rds-data.rst
index a0df02def..233418847 100644
--- a/docs/docs/services/rds-data.rst
+++ b/docs/docs/services/rds-data.rst
@@ -61,7 +61,7 @@ rds-data
                 "http://motoapi.amazonaws.com:5000/moto-api/static/rds-data/statement-results",
                 json=expected_results,
             )
-            resp.status_code.should.equal(201)
+            assert resp.status_code == 201
 
             rdsdata = boto3.client("rds-data", region_name="us-east-1")
             resp = rdsdata.execute_statement(resourceArn="not applicable", secretArn="not applicable", sql="SELECT some FROM thing")
diff --git a/docs/docs/services/rds.rst b/docs/docs/services/rds.rst
index 8741432ee..acaa5fc75 100644
--- a/docs/docs/services/rds.rst
+++ b/docs/docs/services/rds.rst
@@ -58,6 +58,7 @@ rds
 - [ ] delete_blue_green_deployment
 - [ ] delete_custom_db_engine_version
 - [X] delete_db_cluster
+- [ ] delete_db_cluster_automated_backup
 - [ ] delete_db_cluster_endpoint
 - [X] delete_db_cluster_parameter_group
 - [X] delete_db_cluster_snapshot
@@ -76,6 +77,7 @@ rds
 - [ ] describe_account_attributes
 - [ ] describe_blue_green_deployments
 - [ ] describe_certificates
+- [ ] describe_db_cluster_automated_backups
 - [ ] describe_db_cluster_backtracks
 - [ ] describe_db_cluster_endpoints
 - [X] describe_db_cluster_parameter_groups
@@ -169,5 +171,6 @@ rds
 - [X] stop_db_instance
 - [ ] stop_db_instance_automated_backups_replication
 - [ ] switchover_blue_green_deployment
+- [ ] switchover_global_cluster
 - [ ] switchover_read_replica
 
diff --git a/docs/docs/services/s3.rst b/docs/docs/services/s3.rst
index a0a7920d5..aa7351335 100644
--- a/docs/docs/services/s3.rst
+++ b/docs/docs/services/s3.rst
@@ -152,7 +152,7 @@ s3
          - Function aliases (count(*) as cnt)
          - Most functions (only count() is supported)
          - Result is always in JSON
-         - FieldDelimiters and RecordDelimiters are ignored
+         - FieldDelimiters are ignored
         
 
 - [X] upload_part
diff --git a/docs/docs/services/sagemaker.rst b/docs/docs/services/sagemaker.rst
index 71cc9a13e..eb7ba3c36 100644
--- a/docs/docs/services/sagemaker.rst
+++ b/docs/docs/services/sagemaker.rst
@@ -194,6 +194,7 @@ sagemaker
 - [ ] get_lineage_group_policy
 - [ ] get_model_package_group_policy
 - [ ] get_sagemaker_servicecatalog_portfolio_status
+- [ ] get_scaling_configuration_recommendation
 - [ ] get_search_suggestions
 - [ ] import_hub_content
 - [ ] list_actions
diff --git a/docs/docs/services/servicediscovery.rst b/docs/docs/services/servicediscovery.rst
index 196dcc94a..353881ed8 100644
--- a/docs/docs/services/servicediscovery.rst
+++ b/docs/docs/services/servicediscovery.rst
@@ -62,7 +62,7 @@ servicediscovery
 - [X] untag_resource
 - [ ] update_http_namespace
 - [ ] update_instance_custom_health_status
-- [ ] update_private_dns_namespace
-- [ ] update_public_dns_namespace
+- [X] update_private_dns_namespace
+- [X] update_public_dns_namespace
 - [X] update_service
 
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
index 9be813030..704f02753 100644
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -301,11 +301,11 @@ class DynamoDBBackend(BaseBackend):
         self,
         table_name: str,
         keys: Dict[str, Any],
-        projection_expression: Optional[str] = None,
+        projection_expressions: Optional[List[List[str]]] = None,
     ) -> Optional[Item]:
         table = self.get_table(table_name)
         hash_key, range_key = self.get_keys_value(table, keys)
-        return table.get_item(hash_key, range_key, projection_expression)
+        return table.get_item(hash_key, range_key, projection_expressions)
 
     def query(
         self,
@@ -316,7 +316,7 @@ class DynamoDBBackend(BaseBackend):
         limit: int,
         exclusive_start_key: Dict[str, Any],
         scan_index_forward: bool,
-        projection_expression: Optional[str],
+        projection_expressions: Optional[List[List[str]]],
         index_name: Optional[str] = None,
         expr_names: Optional[Dict[str, str]] = None,
         expr_values: Optional[Dict[str, str]] = None,
@@ -339,7 +339,7 @@ class DynamoDBBackend(BaseBackend):
             limit,
             exclusive_start_key,
             scan_index_forward,
-            projection_expression,
+            projection_expressions,
             index_name,
             filter_expression_op,
             **filter_kwargs,
@@ -355,7 +355,7 @@ class DynamoDBBackend(BaseBackend):
         expr_names: Dict[str, Any],
         expr_values: Dict[str, Any],
         index_name: str,
-        projection_expression: Optional[str],
+        projection_expression: Optional[List[List[str]]],
     ) -> Tuple[List[Item], int, Optional[Dict[str, Any]]]:
         table = self.get_table(table_name)
 
diff --git a/moto/dynamodb/models/dynamo_type.py b/moto/dynamodb/models/dynamo_type.py
index 0ea58b49d..992100c06 100644
--- a/moto/dynamodb/models/dynamo_type.py
+++ b/moto/dynamodb/models/dynamo_type.py
@@ -418,13 +418,12 @@ class Item(BaseModel):
                     f"{action} action not support for update_with_attribute_updates"
                 )
 
-    def project(self, projection_expression: str) -> "Item":
+    def project(self, projection_expressions: List[List[str]]) -> "Item":
         # Returns a new Item with only the dictionary-keys that match the provided projection_expression
         # Will return an empty Item if the expression does not match anything
         result: Dict[str, Any] = dict()
-        expressions = [x.strip() for x in projection_expression.split(",")]
-        for expr in expressions:
-            x = find_nested_key(expr.split("."), self.to_regular_json())
+        for expr in projection_expressions:
+            x = find_nested_key(expr, self.to_regular_json())
             merge_dicts(result, x)
 
         return Item(
diff --git a/moto/dynamodb/models/table.py b/moto/dynamodb/models/table.py
index 78f18e809..45037afc9 100644
--- a/moto/dynamodb/models/table.py
+++ b/moto/dynamodb/models/table.py
@@ -50,12 +50,18 @@ class SecondaryIndex(BaseModel):
             ]
 
             if projection_type == "KEYS_ONLY":
-                item = item.project(",".join(key_attributes))
+                # 'project' expects lists of lists of strings
+                # project([["attr1"], ["nested", "attr2"]]
+                #
+                # In our case, we need to convert
+                # ["key1", "key2"]
+                # into
+                # [["key1"], ["key2"]]
+                item = item.project([[attr] for attr in key_attributes])
             elif projection_type == "INCLUDE":
-                allowed_attributes = key_attributes + self.projection.get(
-                    "NonKeyAttributes", []
-                )
-                item = item.project(",".join(allowed_attributes))
+                allowed_attributes = key_attributes
+                allowed_attributes.extend(self.projection.get("NonKeyAttributes", []))
+                item = item.project([[attr] for attr in allowed_attributes])
             # ALL is handled implicitly by not filtering
         return item
 
@@ -592,7 +598,7 @@ class Table(CloudFormationModel):
         self,
         hash_key: DynamoType,
         range_key: Optional[DynamoType] = None,
-        projection_expression: Optional[str] = None,
+        projection_expression: Optional[List[List[str]]] = None,
     ) -> Optional[Item]:
         if self.has_range_key and not range_key:
             raise MockValidationException(
@@ -637,7 +643,7 @@ class Table(CloudFormationModel):
         limit: int,
         exclusive_start_key: Dict[str, Any],
         scan_index_forward: bool,
-        projection_expression: Optional[str],
+        projection_expressions: Optional[List[List[str]]],
         index_name: Optional[str] = None,
         filter_expression: Any = None,
         **filter_kwargs: Any,
@@ -754,8 +760,8 @@ class Table(CloudFormationModel):
         if filter_expression is not None:
             results = [item for item in results if filter_expression.expr(item)]
 
-        if projection_expression:
-            results = [r.project(projection_expression) for r in results]
+        if projection_expressions:
+            results = [r.project(projection_expressions) for r in results]
 
         return results, scanned_count, last_evaluated_key
 
@@ -799,7 +805,7 @@ class Table(CloudFormationModel):
         exclusive_start_key: Dict[str, Any],
         filter_expression: Any = None,
         index_name: Optional[str] = None,
-        projection_expression: Optional[str] = None,
+        projection_expression: Optional[List[List[str]]] = None,
     ) -> Tuple[List[Item], int, Optional[Dict[str, Any]]]:
         results = []
         scanned_count = 0
diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py
index 8f94fcb6f..d64a10e0b 100644
--- a/moto/dynamodb/responses.py
+++ b/moto/dynamodb/responses.py
@@ -556,11 +556,11 @@ class DynamoHandler(BaseResponse):
                 )
 
         expression_attribute_names = expression_attribute_names or {}
-        projection_expression = self._adjust_projection_expression(
+        projection_expressions = self._adjust_projection_expression(
             projection_expression, expression_attribute_names
         )
 
-        item = self.dynamodb_backend.get_item(name, key, projection_expression)
+        item = self.dynamodb_backend.get_item(name, key, projection_expressions)
         if item:
             item_dict = item.describe_attrs(attributes=None)
             return dynamo_json_dump(item_dict)
@@ -608,14 +608,14 @@ class DynamoHandler(BaseResponse):
                 "ExpressionAttributeNames", {}
             )
 
-            projection_expression = self._adjust_projection_expression(
+            projection_expressions = self._adjust_projection_expression(
                 projection_expression, expression_attribute_names
             )
 
             results["Responses"][table_name] = []
             for key in keys:
                 item = self.dynamodb_backend.get_item(
-                    table_name, key, projection_expression
+                    table_name, key, projection_expressions
                 )
                 if item:
                     # A single operation can retrieve up to 16 MB of data [and] returns a partial result if the response size limit is exceeded
@@ -652,7 +652,7 @@ class DynamoHandler(BaseResponse):
         filter_expression = self._get_filter_expression()
         expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
 
-        projection_expression = self._adjust_projection_expression(
+        projection_expressions = self._adjust_projection_expression(
             projection_expression, expression_attribute_names
         )
 
@@ -720,7 +720,7 @@ class DynamoHandler(BaseResponse):
             limit,
             exclusive_start_key,
             scan_index_forward,
-            projection_expression,
+            projection_expressions,
             index_name=index_name,
             expr_names=expression_attribute_names,
             expr_values=expression_attribute_values,
@@ -743,27 +743,24 @@ class DynamoHandler(BaseResponse):
 
     def _adjust_projection_expression(
         self, projection_expression: Optional[str], expr_attr_names: Dict[str, str]
-    ) -> Optional[str]:
+    ) -> List[List[str]]:
+        """
+        lvl1.lvl2.attr1,lvl1.attr2 --> [["lvl1", "lvl2", "attr1"], ["lvl1", "attr2]]
+        """
+
         def _adjust(expression: str) -> str:
-            return (
-                expr_attr_names[expression]
-                if expression in expr_attr_names
-                else expression
-            )
+            return (expr_attr_names or {}).get(expression, expression)
 
         if projection_expression:
             expressions = [x.strip() for x in projection_expression.split(",")]
             for expression in expressions:
                 check_projection_expression(expression)
-            if expr_attr_names:
-                return ",".join(
-                    [
-                        ".".join([_adjust(expr) for expr in nested_expr.split(".")])
-                        for nested_expr in expressions
-                    ]
-                )
+            return [
+                [_adjust(expr) for expr in nested_expr.split(".")]
+                for nested_expr in expressions
+            ]
 
-        return projection_expression
+        return []
 
     @include_consumed_capacity()
     def scan(self) -> str:
@@ -786,7 +783,7 @@ class DynamoHandler(BaseResponse):
         limit = self.body.get("Limit")
         index_name = self.body.get("IndexName")
 
-        projection_expression = self._adjust_projection_expression(
+        projection_expressions = self._adjust_projection_expression(
             projection_expression, expression_attribute_names
         )
 
@@ -800,7 +797,7 @@ class DynamoHandler(BaseResponse):
                 expression_attribute_names,
                 expression_attribute_values,
                 index_name,
-                projection_expression,
+                projection_expressions,
             )
         except ValueError as err:
             raise MockValidationException(f"Bad Filter Expression: {err}")


Test patch:
diff --git a/tests/test_dynamodb/models/test_item.py b/tests/test_dynamodb/models/test_item.py
index f3ed31097..e26a3375a 100644
--- a/tests/test_dynamodb/models/test_item.py
+++ b/tests/test_dynamodb/models/test_item.py
@@ -34,17 +34,17 @@ class TestFindNestedKeys:
         assert x == y
 
     def test_find_nothing(self):
-        self._project("", result={})
+        self._project([[""]], result={})
 
     def test_find_unknown_key(self):
-        self._project("unknown", result={})
+        self._project([["unknown"]], result={})
 
     def test_project_single_key_string(self):
-        self._project("simplestring", result={"simplestring": "val"})
+        self._project([["simplestring"]], result={"simplestring": "val"})
 
     def test_project_single_key_dict(self):
         self._project(
-            "nesteddict",
+            [["nesteddict"]],
             result={
                 "nesteddict": {
                     "level21": {"ll31": "val", "ll32": "val"},
@@ -59,31 +59,31 @@ class TestFindNestedKeys:
 
     def test_project_nested_key(self):
         self._project(
-            "nesteddict.level21",
+            [["nesteddict", "level21"]],
             result={"nesteddict": {"level21": {"ll31": "val", "ll32": "val"}}},
         )
 
     def test_project_multi_level_nested_key(self):
         self._project(
-            "nesteddict.level21.ll32",
+            [["nesteddict", "level21", "ll32"]],
             result={"nesteddict": {"level21": {"ll32": "val"}}},
         )
 
     def test_project_nested_key__partial_fix(self):
-        self._project("nesteddict.levelunknown", result={})
+        self._project([["nesteddict", "levelunknown"]], result={})
 
     def test_project_nested_key__partial_fix2(self):
-        self._project("nesteddict.unknown.unknown2", result={})
+        self._project([["nesteddict", "unknown", "unknown2"]], result={})
 
     def test_list_index(self):
         self._project(
-            "rootlist[0]",
+            [["rootlist[0]"]],
             result={"rootlist": [{"ll21": {"ll31": "val", "ll32": "val"}}]},
         )
 
     def test_nested_list_index(self):
         self._project(
-            "nesteddict.nestedlist[1]",
+            [["nesteddict", "nestedlist[1]"]],
             result={
                 "nesteddict": {"nestedlist": [{"ll22": {"ll31": "val", "ll32": "val"}}]}
             },
@@ -91,16 +91,16 @@ class TestFindNestedKeys:
 
     def test_nested_obj_in_list(self):
         self._project(
-            "nesteddict.nestedlist[1].ll22.ll31",
+            [["nesteddict", "nestedlist[1]", "ll22", "ll31"]],
             result={"nesteddict": {"nestedlist": [{"ll22": {"ll31": "val"}}]}},
         )
 
     def test_list_unknown_indexes(self):
-        self._project("nesteddict.nestedlist[25]", result={})
+        self._project([["nesteddict", "nestedlist[25]"]], result={})
 
     def test_multiple_projections(self):
         self._project(
-            "nesteddict.nestedlist[1].ll22,rootlist[0]",
+            [["nesteddict", "nestedlist[1]", "ll22"], ["rootlist[0]"]],
             result={
                 "nesteddict": {
                     "nestedlist": [{"ll22": {"ll31": "val", "ll32": "val"}}]
diff --git a/tests/test_dynamodb/test_dynamodb.py b/tests/test_dynamodb/test_dynamodb.py
index e0787bf0a..81e4dc3e6 100644
--- a/tests/test_dynamodb/test_dynamodb.py
+++ b/tests/test_dynamodb/test_dynamodb.py
@@ -886,7 +886,7 @@ def test_nested_projection_expression_using_get_item_with_attr_expression():
             "forum_name": "key1",
             "nested": {
                 "level1": {"id": "id1", "att": "irrelevant"},
-                "level2": {"id": "id2", "include": "all"},
+                "level.2": {"id": "id2", "include": "all"},
                 "level3": {
                     "id": "irrelevant",
                     "children": [{"Name": "child_a"}, {"Name": "child_b"}],
@@ -907,10 +907,10 @@ def test_nested_projection_expression_using_get_item_with_attr_expression():
     result = table.get_item(
         Key={"forum_name": "key1"},
         ProjectionExpression="#nst.level1.id, #nst.#lvl2",
-        ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level2"},
+        ExpressionAttributeNames={"#nst": "nested", "#lvl2": "level.2"},
     )["Item"]
     assert result == {
-        "nested": {"level1": {"id": "id1"}, "level2": {"id": "id2", "include": "all"}}
+        "nested": {"level1": {"id": "id1"}, "level.2": {"id": "id2", "include": "all"}}
     }
     # Assert actual data has not been deleted
     result = table.get_item(Key={"forum_name": "key1"})["Item"]
@@ -919,7 +919,7 @@ def test_nested_projection_expression_using_get_item_with_attr_expression():
         "forum_name": "key1",
         "nested": {
             "level1": {"id": "id1", "att": "irrelevant"},
-            "level2": {"id": "id2", "include": "all"},
+            "level.2": {"id": "id2", "include": "all"},
             "level3": {
                 "id": "irrelevant",
                 "children": [{"Name": "child_a"}, {"Name": "child_b"}],
diff --git a/tests/test_rds/test_rds_clusters.py b/tests/test_rds/test_rds_clusters.py
index 678714d34..c047e276e 100644
--- a/tests/test_rds/test_rds_clusters.py
+++ b/tests/test_rds/test_rds_clusters.py
@@ -481,8 +481,9 @@ def test_create_db_cluster_snapshot():
 def test_create_db_cluster_snapshot_copy_tags():
     conn = boto3.client("rds", region_name="us-west-2")
 
+    dbci = "db-primary-1"
     conn.create_db_cluster(
-        DBClusterIdentifier="db-primary-1",
+        DBClusterIdentifier=dbci,
         AllocatedStorage=10,
         Engine="postgres",
         DatabaseName="staging-postgres",
@@ -495,11 +496,11 @@ def test_create_db_cluster_snapshot_copy_tags():
     )
 
     snapshot = conn.create_db_cluster_snapshot(
-        DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="g-1"
+        DBClusterIdentifier=dbci, DBClusterSnapshotIdentifier="g-1"
     ).get("DBClusterSnapshot")
 
     assert snapshot.get("Engine") == "postgres"
-    assert snapshot.get("DBClusterIdentifier") == "db-primary-1"
+    assert snapshot.get("DBClusterIdentifier") == dbci
     assert snapshot.get("DBClusterSnapshotIdentifier") == "g-1"
 
     result = conn.list_tags_for_resource(ResourceName=snapshot["DBClusterSnapshotArn"])
@@ -508,6 +509,14 @@ def test_create_db_cluster_snapshot_copy_tags():
         {"Value": "bar1", "Key": "foo1"},
     ]
 
+    snapshot = conn.describe_db_cluster_snapshots(DBClusterIdentifier=dbci)[
+        "DBClusterSnapshots"
+    ][0]
+    assert snapshot["TagList"] == [
+        {"Key": "foo", "Value": "bar"},
+        {"Key": "foo1", "Value": "bar1"},
+    ]
+
 
 @mock_rds
 def test_copy_db_cluster_snapshot_fails_for_unknown_snapshot():