Repository: getmoto/moto
Pull request #7723 (2024-05-28 10:05:02)

Fix BackendDict instantiation service names
This PR updates certain BackendDict initialisations so that they use proper Botocore service name.

Earlier, these services used `ec2` or `lambda` since `boto3.Session().get_available_regions(...)` used to return an empty list.

There are still a few instantiations remaining that were not updated for the above reason, these are:
- `redshiftdata` should use `redshift-data`
- `apigatewaymanagementapi` should use `apigatewaymanagementapi`
- `bedrockagent`
- `ec2instanceconnect` should use `ec2-instance-connect`


Patch:
diff --git a/docs/docs/configuration/environment_variables.rst b/docs/docs/configuration/environment_variables.rst
index 2520bc0cf..2cc0382c0 100644
--- a/docs/docs/configuration/environment_variables.rst
+++ b/docs/docs/configuration/environment_variables.rst
@@ -32,6 +32,8 @@ The following is a non-exhaustive list of the environment variables that can be
 +-------------------------------+----------+-----------+-------------------------------------------------------------------------------------------------+
 | MOTO_S3_CUSTOM_ENDPOINTS      | str      |           | See :ref:`s3`.                                                                                  |
 +-------------------------------+----------+-----------+-------------------------------------------------------------------------------------------------+
+| MOTO_S3_DEFAULT_MAX_KEYS      | str      |           | See :ref:`s3`.                                                                                  |
++-------------------------------+----------+-----------+-------------------------------------------------------------------------------------------------+
 | MOTO_PRETTIFY_RESPONSES       | bool     | False     | Prettify responses from Moto, making it easier to read and debug.                               |
 +-------------------------------+----------+-----------+-------------------------------------------------------------------------------------------------+
 
diff --git a/docs/docs/services/s3.rst b/docs/docs/services/s3.rst
index 155e1ff07..1d599a25f 100644
--- a/docs/docs/services/s3.rst
+++ b/docs/docs/services/s3.rst
@@ -84,8 +84,26 @@ s3
 - [ ] list_directory_buckets
 - [ ] list_multipart_uploads
 - [X] list_object_versions
+  
+        The default value for the MaxKeys-argument is 100. This can be configured with an environment variable:
+
+        MOTO_S3_DEFAULT_MAX_KEYS=5
+        
+
 - [X] list_objects
+  
+        The default value for the MaxKeys-argument is 100. This can be configured with an environment variable:
+
+        MOTO_S3_DEFAULT_MAX_KEYS=5
+        
+
 - [X] list_objects_v2
+  
+        The default value for the MaxKeys-argument is 100. This can be configured with an environment variable:
+
+        MOTO_S3_DEFAULT_MAX_KEYS=5
+        
+
 - [X] list_parts
 - [X] put_bucket_accelerate_configuration
 - [X] put_bucket_acl
diff --git a/moto/applicationautoscaling/models.py b/moto/applicationautoscaling/models.py
index d3d2a1c64..426069f8c 100644
--- a/moto/applicationautoscaling/models.py
+++ b/moto/applicationautoscaling/models.py
@@ -673,4 +673,6 @@ class FakeScheduledAction(BaseModel):
         self.end_time = end_time
 
 
-applicationautoscaling_backends = BackendDict(ApplicationAutoscalingBackend, "ec2")
+applicationautoscaling_backends = BackendDict(
+    ApplicationAutoscalingBackend, "application-autoscaling"
+)
diff --git a/moto/elb/models.py b/moto/elb/models.py
index 330bcc6b5..83dc01c3a 100644
--- a/moto/elb/models.py
+++ b/moto/elb/models.py
@@ -665,5 +665,4 @@ class ELBBackend(BaseBackend):
         return load_balancer.subnets
 
 
-# Use the same regions as EC2
-elb_backends = BackendDict(ELBBackend, "ec2")
+elb_backends = BackendDict(ELBBackend, "elb")
diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
index e7081d726..7dd4d4d75 100644
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -1979,4 +1979,4 @@ Member must satisfy regular expression pattern: {expression}"
         return resource
 
 
-elbv2_backends = BackendDict(ELBv2Backend, "ec2")
+elbv2_backends = BackendDict(ELBv2Backend, "elbv2")
diff --git a/moto/iotdata/models.py b/moto/iotdata/models.py
index 8f25f13f0..880466538 100644
--- a/moto/iotdata/models.py
+++ b/moto/iotdata/models.py
@@ -221,4 +221,4 @@ class IoTDataPlaneBackend(BaseBackend):
         ]
 
 
-iotdata_backends = BackendDict(IoTDataPlaneBackend, "iot")
+iotdata_backends = BackendDict(IoTDataPlaneBackend, "iot-data")
diff --git a/moto/opsworks/models.py b/moto/opsworks/models.py
index 1add8b275..a23ade1d3 100644
--- a/moto/opsworks/models.py
+++ b/moto/opsworks/models.py
@@ -630,4 +630,4 @@ class OpsWorksBackend(BaseBackend):
         self.instances[instance_id].start()
 
 
-opsworks_backends = BackendDict(OpsWorksBackend, "ec2")
+opsworks_backends = BackendDict(OpsWorksBackend, "opsworks")
diff --git a/moto/s3/models.py b/moto/s3/models.py
index 86d2ce921..b18b6def8 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -1652,6 +1652,14 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
 
     _-_-_-_
 
+    When listing objects, the default max-keys value is 1000, just as with AWS. Use the following environment variable to configure this this:
+
+    .. sourcecode:: bash
+
+        MOTO_S3_DEFAULT_MAX_KEYS=256
+
+    _-_-_-_
+
     CrossAccount access is allowed by default. If you want Moto to throw an AccessDenied-error when accessing a bucket in another account, use this environment variable:
 
     .. sourcecode:: bash
@@ -1881,6 +1889,11 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
     ) -> Tuple[
         List[FakeKey], List[str], List[FakeDeleteMarker], Optional[str], Optional[str]
     ]:
+        """
+        The default value for the MaxKeys-argument is 100. This can be configured with an environment variable:
+
+        MOTO_S3_DEFAULT_MAX_KEYS=5
+        """
         bucket = self.get_bucket(bucket_name)
 
         common_prefixes: Set[str] = set()
@@ -2571,6 +2584,11 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         marker: Optional[str],
         max_keys: Optional[int],
     ) -> Tuple[Set[FakeKey], Set[str], bool, Optional[str]]:
+        """
+        The default value for the MaxKeys-argument is 100. This can be configured with an environment variable:
+
+        MOTO_S3_DEFAULT_MAX_KEYS=5
+        """
         key_results = set()
         folder_results = set()
         if prefix:
@@ -2624,6 +2642,11 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
         start_after: Optional[str],
         max_keys: int,
     ) -> Tuple[Set[Union[FakeKey, str]], bool, Optional[str]]:
+        """
+        The default value for the MaxKeys-argument is 100. This can be configured with an environment variable:
+
+        MOTO_S3_DEFAULT_MAX_KEYS=5
+        """
         result_keys, result_folders, _, _ = self.list_objects(
             bucket, prefix, delimiter, marker=None, max_keys=None
         )
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
index dd9e9c91c..c53588c78 100644
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -619,7 +619,9 @@ class S3Response(BaseResponse):
         elif "versions" in querystring:
             delimiter = querystring.get("delimiter", [None])[0]
             key_marker = querystring.get("key-marker", [None])[0]
-            max_keys = int(querystring.get("max-keys", [1000])[0])
+            max_keys = int(
+                querystring.get("max-keys", [settings.get_s3_default_max_keys()])[0]
+            )
             prefix = querystring.get("prefix", [""])[0]
             version_id_marker = querystring.get("version-id-marker", [None])[0]
 
@@ -693,7 +695,9 @@ class S3Response(BaseResponse):
         if prefix and isinstance(prefix, bytes):
             prefix = prefix.decode("utf-8")
         delimiter = querystring.get("delimiter", [None])[0]
-        max_keys = int(querystring.get("max-keys", [1000])[0])
+        max_keys = int(
+            querystring.get("max-keys", [settings.get_s3_default_max_keys()])[0]
+        )
         marker = querystring.get("marker", [None])[0]
         encoding_type = querystring.get("encoding-type", [None])[0]
 
@@ -756,7 +760,9 @@ class S3Response(BaseResponse):
         delimiter = querystring.get("delimiter", [None])[0]
 
         fetch_owner = querystring.get("fetch-owner", [False])[0]
-        max_keys = int(querystring.get("max-keys", [1000])[0])
+        max_keys = int(
+            querystring.get("max-keys", [settings.get_s3_default_max_keys()])[0]
+        )
         start_after = querystring.get("start-after", [None])[0]
         encoding_type = querystring.get("encoding-type", [None])[0]
 
diff --git a/moto/settings.py b/moto/settings.py
index 263839c81..a537d0143 100644
--- a/moto/settings.py
+++ b/moto/settings.py
@@ -72,6 +72,10 @@ def get_s3_default_key_buffer_size() -> int:
     )
 
 
+def get_s3_default_max_keys() -> int:
+    return int(os.environ.get("MOTO_S3_DEFAULT_MAX_KEYS", 1000))
+
+
 def s3_allow_crossdomain_access() -> bool:
     return os.environ.get("MOTO_S3_ALLOW_CROSSACCOUNT_ACCESS", "true").lower() == "true"
 
diff --git a/moto/signer/models.py b/moto/signer/models.py
index 2c46b07bd..dced5f59e 100644
--- a/moto/signer/models.py
+++ b/moto/signer/models.py
@@ -212,6 +212,4 @@ class SignerBackend(BaseBackend):
         self.tagger.untag_resource_using_names(resource_arn, tag_keys)
 
 
-# Using the lambda-regions
-# boto3.Session().get_available_regions("signer") still returns an empty list
-signer_backends = BackendDict(SignerBackend, "lambda")
+signer_backends = BackendDict(SignerBackend, "signer")


Test patch:
diff --git a/.github/workflows/tests_sdk_ruby.yml b/.github/workflows/tests_sdk_ruby.yml
index fa6f0f230..4997aa25c 100644
--- a/.github/workflows/tests_sdk_ruby.yml
+++ b/.github/workflows/tests_sdk_ruby.yml
@@ -13,7 +13,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - name: Set up Ruby ${{ matrix.ruby-version }}
-        uses: ruby/setup-ruby@6615b4b8a02c78c4d711c06df4b8a20aa685a45d
+        uses: ruby/setup-ruby@943103cae7d3f1bb1e4951d5fcc7928b40e4b742
         with:
           ruby-version: ${{ matrix.ruby-version }}
       - name: Set up Python 3.8
diff --git a/tests/test_dynamodb/test_dynamodb_update_expressions.py b/tests/test_dynamodb/test_dynamodb_update_expressions.py
index c353cf4f4..5ffcedbac 100644
--- a/tests/test_dynamodb/test_dynamodb_update_expressions.py
+++ b/tests/test_dynamodb/test_dynamodb_update_expressions.py
@@ -180,8 +180,8 @@ def test_update_item_add_empty_set(table_name=None):
     err = exc.value.response["Error"]
     assert err["Code"] == "ValidationException"
     assert (
-        err["Message"]
-        == "ExpressionAttributeValues contains invalid value: One or more parameter values were invalid: An string set  may not be empty"
+        "ExpressionAttributeValues contains invalid value: One or more parameter values were invalid: An string set  may not be empty"
+        in err["Message"]
     )
 
     assert dynamodb.scan(TableName=table_name)["Items"] == [
diff --git a/tests/test_s3/test_s3_list_object_versions.py b/tests/test_s3/test_s3_list_object_versions.py
index 3719eb2c3..0ec0466c8 100644
--- a/tests/test_s3/test_s3_list_object_versions.py
+++ b/tests/test_s3/test_s3_list_object_versions.py
@@ -1,10 +1,11 @@
 from time import sleep
+from unittest import SkipTest, mock
 from uuid import uuid4
 
 import boto3
 import pytest
 
-from moto import mock_aws
+from moto import mock_aws, settings
 from moto.s3.responses import DEFAULT_REGION_NAME
 
 from . import s3_aws_verified
@@ -639,3 +640,25 @@ def enable_versioning(bucket_name, s3_client):
     while resp.get("Status") != "Enabled":
         sleep(0.1)
         resp = s3_client.get_bucket_versioning(Bucket=bucket_name)
+
+
+@mock_aws
+@mock.patch.dict("os.environ", {"MOTO_S3_DEFAULT_MAX_KEYS": "5"})
+def test_list_object_versions_with_custom_limit():
+    if not settings.TEST_DECORATOR_MODE:
+        raise SkipTest("Can only set env var in DecoratorMode")
+    s3_client = boto3.client("s3", region_name=DEFAULT_REGION_NAME)
+    s3_resource = boto3.resource("s3", region_name=DEFAULT_REGION_NAME)
+    bucket_name = "foobar"
+    bucket = s3_resource.Bucket(bucket_name)
+    bucket.create()
+    bucket.Versioning().enable()
+
+    for i in range(10):
+        s3_client.put_object(
+            Bucket=bucket_name, Key="obj", Body=b"ver" + str(i).encode("utf-8")
+        )
+
+    page1 = s3_client.list_object_versions(Bucket=bucket_name)
+    assert page1["MaxKeys"] == 5
+    assert len(page1["Versions"]) == 5
diff --git a/tests/test_s3/test_s3_list_objects.py b/tests/test_s3/test_s3_list_objects.py
index ffb500f71..f436aa22f 100644
--- a/tests/test_s3/test_s3_list_objects.py
+++ b/tests/test_s3/test_s3_list_objects.py
@@ -1,6 +1,10 @@
+from unittest import SkipTest, mock
+
 import boto3
 import pytest
 
+from moto import mock_aws, settings
+
 from . import s3_aws_verified
 
 
@@ -50,3 +54,36 @@ def test_s3_filter_with_manual_marker(name=None) -> None:
     assert pages[1] == ["key_05", "key_06", "key_07", "key_08", "key_09"]
     assert pages[2] == ["key_10", "key_11", "key_12", "key_13", "key_14"]
     assert pages[3] == ["key_15", "key_16"]
+
+
+@mock_aws
+@mock.patch.dict("os.environ", {"MOTO_S3_DEFAULT_MAX_KEYS": "5"})
+def test_list_object_versions_with_custom_limit():
+    if not settings.TEST_DECORATOR_MODE:
+        raise SkipTest("Can only set env var in DecoratorMode")
+    s3_client = boto3.client("s3", region_name="us-east-1")
+    s3_resource = boto3.resource("s3", region_name="us-east-1")
+    bucket_name = "foobar"
+    bucket = s3_resource.Bucket(bucket_name)
+    bucket.create()
+
+    for i in range(8):
+        s3_client.put_object(Bucket=bucket_name, Key=f"obj_{i}", Body=b"data")
+
+    page1 = s3_client.list_objects(Bucket=bucket_name)
+    assert page1["MaxKeys"] == 5
+    assert len(page1["Contents"]) == 5
+
+    page2 = s3_client.list_objects(Bucket=bucket_name, Marker=page1["NextMarker"])
+    assert page2["MaxKeys"] == 5
+    assert len(page2["Contents"]) == 3
+
+    page1 = s3_client.list_objects_v2(Bucket=bucket_name)
+    assert page1["MaxKeys"] == 5
+    assert len(page1["Contents"]) == 5
+
+    page2 = s3_client.list_objects_v2(
+        Bucket=bucket_name, ContinuationToken=page1["NextContinuationToken"]
+    )
+    assert page2["MaxKeys"] == 5
+    assert len(page2["Contents"]) == 3