localstack-core 4.7.1.dev96__py3-none-any.whl → 4.7.1.dev98__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of localstack-core might be problematic. Click here for more details.

Files changed (65) hide show
  1. localstack/aws/mocking.py +2 -2
  2. localstack/aws/protocol/parser.py +6 -6
  3. localstack/aws/protocol/serializer.py +5 -5
  4. localstack/aws/scaffold.py +1 -1
  5. localstack/cli/localstack.py +1 -1
  6. localstack/cli/plugins.py +1 -1
  7. localstack/config.py +5 -5
  8. localstack/dev/run/configurators.py +1 -4
  9. localstack/runtime/hooks.py +1 -1
  10. localstack/runtime/init.py +1 -1
  11. localstack/runtime/main.py +5 -5
  12. localstack/services/apigateway/helpers.py +1 -4
  13. localstack/services/apigateway/legacy/helpers.py +7 -8
  14. localstack/services/apigateway/legacy/integration.py +4 -3
  15. localstack/services/apigateway/legacy/invocations.py +2 -2
  16. localstack/services/apigateway/legacy/provider.py +4 -4
  17. localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
  18. localstack/services/cloudformation/api_utils.py +4 -8
  19. localstack/services/cloudformation/cfn_utils.py +1 -1
  20. localstack/services/cloudformation/engine/entities.py +1 -2
  21. localstack/services/cloudformation/engine/template_deployer.py +2 -2
  22. localstack/services/cloudformation/engine/v2/change_set_model_transform.py +2 -2
  23. localstack/services/cloudformation/provider.py +2 -2
  24. localstack/services/cloudformation/service_models.py +1 -1
  25. localstack/services/dynamodb/server.py +1 -1
  26. localstack/services/edge.py +1 -1
  27. localstack/services/events/v1/provider.py +5 -5
  28. localstack/services/kinesis/provider.py +1 -1
  29. localstack/services/plugins.py +6 -6
  30. localstack/services/ssm/provider.py +1 -1
  31. localstack/state/pickle.py +1 -1
  32. localstack/testing/pytest/fixtures.py +3 -3
  33. localstack/utils/analytics/metadata.py +1 -1
  34. localstack/utils/aws/arns.py +7 -20
  35. localstack/utils/aws/aws_responses.py +1 -1
  36. localstack/utils/aws/dead_letter_queue.py +1 -5
  37. localstack/utils/aws/resources.py +1 -1
  38. localstack/utils/aws/templating.py +1 -1
  39. localstack/utils/bootstrap.py +4 -4
  40. localstack/utils/container_utils/docker_cmd_client.py +19 -19
  41. localstack/utils/crypto.py +10 -10
  42. localstack/utils/diagnose.py +1 -1
  43. localstack/utils/files.py +2 -6
  44. localstack/utils/functions.py +1 -1
  45. localstack/utils/http.py +5 -5
  46. localstack/utils/net.py +3 -2
  47. localstack/utils/objects.py +1 -1
  48. localstack/utils/run.py +2 -2
  49. localstack/utils/serving.py +1 -1
  50. localstack/utils/strings.py +5 -5
  51. localstack/utils/testutil.py +3 -4
  52. localstack/utils/time.py +1 -1
  53. localstack/utils/xray/traceid.py +1 -1
  54. localstack/version.py +2 -2
  55. {localstack_core-4.7.1.dev96.dist-info → localstack_core-4.7.1.dev98.dist-info}/METADATA +1 -1
  56. {localstack_core-4.7.1.dev96.dist-info → localstack_core-4.7.1.dev98.dist-info}/RECORD +64 -64
  57. localstack_core-4.7.1.dev98.dist-info/plux.json +1 -0
  58. localstack_core-4.7.1.dev96.dist-info/plux.json +0 -1
  59. {localstack_core-4.7.1.dev96.data → localstack_core-4.7.1.dev98.data}/scripts/localstack +0 -0
  60. {localstack_core-4.7.1.dev96.data → localstack_core-4.7.1.dev98.data}/scripts/localstack-supervisor +0 -0
  61. {localstack_core-4.7.1.dev96.data → localstack_core-4.7.1.dev98.data}/scripts/localstack.bat +0 -0
  62. {localstack_core-4.7.1.dev96.dist-info → localstack_core-4.7.1.dev98.dist-info}/WHEEL +0 -0
  63. {localstack_core-4.7.1.dev96.dist-info → localstack_core-4.7.1.dev98.dist-info}/entry_points.txt +0 -0
  64. {localstack_core-4.7.1.dev96.dist-info → localstack_core-4.7.1.dev98.dist-info}/licenses/LICENSE.txt +0 -0
  65. {localstack_core-4.7.1.dev96.dist-info → localstack_core-4.7.1.dev98.dist-info}/top_level.txt +0 -0
localstack/aws/mocking.py CHANGED
@@ -224,7 +224,7 @@ custom_arns = {
224
224
  def generate_instance(shape: Shape, graph: ShapeGraph) -> Instance | None:
225
225
  if shape is None:
226
226
  return None
227
- raise ValueError("could not generate shape for type %s" % shape.type_name)
227
+ raise ValueError(f"could not generate shape for type {shape.type_name}")
228
228
 
229
229
 
230
230
  @generate_instance.register
@@ -383,7 +383,7 @@ def _(shape: Shape, graph: ShapeGraph) -> int | float | bool | bytes | date:
383
383
  if shape.type_name == "timestamp":
384
384
  return datetime.now()
385
385
 
386
- raise ValueError("unknown type %s" % shape.type_name)
386
+ raise ValueError(f"unknown type {shape.type_name}")
387
387
 
388
388
 
389
389
  def generate_response(operation: OperationModel):
@@ -265,12 +265,12 @@ class RequestParser(abc.ABC):
265
265
  if uri_param_name in uri_params:
266
266
  payload = uri_params[uri_param_name]
267
267
  else:
268
- raise UnknownParserError("Unknown shape location '%s'." % location)
268
+ raise UnknownParserError(f"Unknown shape location '{location}'.")
269
269
  else:
270
270
  # If we don't have to use a specific location, we use the node
271
271
  payload = node
272
272
 
273
- fn_name = "_parse_%s" % shape.type_name
273
+ fn_name = f"_parse_{shape.type_name}"
274
274
  handler = getattr(self, fn_name, self._noop_parser)
275
275
  try:
276
276
  return handler(request, shape, payload, uri_params) if payload is not None else None
@@ -322,7 +322,7 @@ class RequestParser(abc.ABC):
322
322
  return True
323
323
  if value == "false":
324
324
  return False
325
- raise ValueError("cannot parse boolean value %s" % node)
325
+ raise ValueError(f"cannot parse boolean value {node}")
326
326
 
327
327
  @_text_content
328
328
  def _noop_parser(self, _, __, node: Any, ___):
@@ -336,7 +336,7 @@ class RequestParser(abc.ABC):
336
336
  if timestamp_format is None:
337
337
  timestamp_format = self.TIMESTAMP_FORMAT
338
338
  timestamp_format = timestamp_format.lower()
339
- converter = getattr(self, "_timestamp_%s" % timestamp_format)
339
+ converter = getattr(self, f"_timestamp_{timestamp_format}")
340
340
  final_value = converter(value)
341
341
  return final_value
342
342
 
@@ -740,7 +740,7 @@ class RestXMLRequestParser(BaseRestRequestParser):
740
740
  elif tag_name == value_location_name:
741
741
  val_name = self._parse_shape(request, value_shape, single_pair, uri_params)
742
742
  else:
743
- raise ProtocolParserError("Unknown tag: %s" % tag_name)
743
+ raise ProtocolParserError(f"Unknown tag: {tag_name}")
744
744
  parsed[key_name] = val_name
745
745
  return parsed
746
746
 
@@ -786,7 +786,7 @@ class RestXMLRequestParser(BaseRestRequestParser):
786
786
  root = parser.close()
787
787
  except ETree.ParseError as e:
788
788
  raise ProtocolParserError(
789
- "Unable to parse request (%s), invalid XML received:\n%s" % (e, xml_string)
789
+ f"Unable to parse request ({e}), invalid XML received:\n{xml_string}"
790
790
  ) from e
791
791
  return root
792
792
 
@@ -527,7 +527,7 @@ class ResponseSerializer(abc.ABC):
527
527
  timestamp_format = self.TIMESTAMP_FORMAT
528
528
  timestamp_format = timestamp_format.lower()
529
529
  datetime_obj = parse_to_aware_datetime(value)
530
- converter = getattr(self, "_timestamp_%s" % timestamp_format)
530
+ converter = getattr(self, f"_timestamp_{timestamp_format}")
531
531
  final_value = converter(datetime_obj)
532
532
  return final_value
533
533
 
@@ -689,7 +689,7 @@ class BaseXMLResponseSerializer(ResponseSerializer):
689
689
  name = shape.serialization.get("resultWrapper")
690
690
 
691
691
  try:
692
- method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize)
692
+ method = getattr(self, f"_serialize_type_{shape.type_name}", self._default_serialize)
693
693
  method(xmlnode, params, shape, name, mime_type)
694
694
  except (TypeError, ValueError, AttributeError) as e:
695
695
  raise ProtocolSerializerError(
@@ -705,7 +705,7 @@ class BaseXMLResponseSerializer(ResponseSerializer):
705
705
  namespace_metadata = shape.serialization["xmlNamespace"]
706
706
  attribute_name = "xmlns"
707
707
  if namespace_metadata.get("prefix"):
708
- attribute_name += ":%s" % namespace_metadata["prefix"]
708
+ attribute_name += ":{}".format(namespace_metadata["prefix"])
709
709
  structure_node.attrib[attribute_name] = namespace_metadata["uri"]
710
710
  for key, value in params.items():
711
711
  if value is None:
@@ -1261,7 +1261,7 @@ class JSONResponseSerializer(ResponseSerializer):
1261
1261
  else:
1262
1262
  json_version = operation_model.metadata.get("jsonVersion")
1263
1263
  if json_version is not None:
1264
- response.headers["Content-Type"] = "application/x-amz-json-%s" % json_version
1264
+ response.headers["Content-Type"] = f"application/x-amz-json-{json_version}"
1265
1265
  response.set_response(
1266
1266
  self._serialize_body_params(parameters, shape, operation_model, mime_type, request_id)
1267
1267
  )
@@ -1286,7 +1286,7 @@ class JSONResponseSerializer(ResponseSerializer):
1286
1286
  def _serialize(self, body: dict, value: Any, shape, key: str | None, mime_type: str):
1287
1287
  """This method dynamically invokes the correct `_serialize_type_*` method for each shape type."""
1288
1288
  try:
1289
- method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize)
1289
+ method = getattr(self, f"_serialize_type_{shape.type_name}", self._default_serialize)
1290
1290
  method(body, value, shape, key, mime_type)
1291
1291
  except (TypeError, ValueError, AttributeError) as e:
1292
1292
  raise ProtocolSerializerError(
@@ -221,7 +221,7 @@ class ShapeNode:
221
221
 
222
222
  def _print_as_typed_dict(self, output, doc=True, quote_types=False):
223
223
  name = to_valid_python_name(self.shape.name)
224
- output.write('%s = TypedDict("%s", {\n' % (name, name))
224
+ output.write(f'{name} = TypedDict("{name}", {{\n')
225
225
  for k, v in self.shape.members.items():
226
226
  member_name = to_valid_python_name(v.name)
227
227
  # check if the member name is the same as the type name (recursive types need to use forward references)
@@ -602,7 +602,7 @@ def cmd_stop() -> None:
602
602
 
603
603
  try:
604
604
  DOCKER_CLIENT.stop_container(container_name)
605
- console.print("container stopped: %s" % container_name)
605
+ console.print(f"container stopped: {container_name}")
606
606
  except NoSuchContainer:
607
607
  raise CLIError(
608
608
  f'Expected a running LocalStack container named "{container_name}", but found none'
localstack/cli/plugins.py CHANGED
@@ -56,7 +56,7 @@ def find(where, exclude, include, output):
56
56
  elif output == "dict":
57
57
  rprint(dict(plugins))
58
58
  else:
59
- raise CLIError("unknown output format %s" % output)
59
+ raise CLIError(f"unknown output format {output}")
60
60
 
61
61
 
62
62
  @cli.command("list")
localstack/config.py CHANGED
@@ -286,7 +286,7 @@ def ping(host):
286
286
  """Returns True if the host responds to a ping request"""
287
287
  is_in_windows = is_windows()
288
288
  ping_opts = "-n 1 -w 2000" if is_in_windows else "-c 1 -W 2"
289
- args = "ping %s %s" % (ping_opts, host)
289
+ args = f"ping {ping_opts} {host}"
290
290
  return (
291
291
  subprocess.call(
292
292
  args, shell=not is_in_windows, stdout=subprocess.PIPE, stderr=subprocess.PIPE
@@ -435,8 +435,8 @@ TMP_FOLDER = os.path.join(tempfile.gettempdir(), "localstack")
435
435
  VOLUME_DIR = os.environ.get("LOCALSTACK_VOLUME_DIR", "").strip() or TMP_FOLDER
436
436
 
437
437
  # fix for Mac OS, to be able to mount /var/folders in Docker
438
- if TMP_FOLDER.startswith("/var/folders/") and os.path.exists("/private%s" % TMP_FOLDER):
439
- TMP_FOLDER = "/private%s" % TMP_FOLDER
438
+ if TMP_FOLDER.startswith("/var/folders/") and os.path.exists(f"/private{TMP_FOLDER}"):
439
+ TMP_FOLDER = f"/private{TMP_FOLDER}"
440
440
 
441
441
  # whether to enable verbose debug logging ("LOG" is used when using the CLI with LOCALSTACK_LOG instead of LS_LOG)
442
442
  LS_LOG = eval_log_type("LS_LOG") or eval_log_type("LOG")
@@ -1237,8 +1237,8 @@ def use_custom_dns():
1237
1237
 
1238
1238
 
1239
1239
  # s3 virtual host name
1240
- S3_VIRTUAL_HOSTNAME = "s3.%s" % LOCALSTACK_HOST.host
1241
- S3_STATIC_WEBSITE_HOSTNAME = "s3-website.%s" % LOCALSTACK_HOST.host
1240
+ S3_VIRTUAL_HOSTNAME = f"s3.{LOCALSTACK_HOST.host}"
1241
+ S3_STATIC_WEBSITE_HOSTNAME = f"s3-website.{LOCALSTACK_HOST.host}"
1242
1242
 
1243
1243
  BOTO_WAITER_DELAY = int(os.environ.get("BOTO_WAITER_DELAY") or "1")
1244
1244
  BOTO_WAITER_MAX_ATTEMPTS = int(os.environ.get("BOTO_WAITER_MAX_ATTEMPTS") or "120")
@@ -363,10 +363,7 @@ def _list_files_in_container_image(container_client: ContainerClient, image_name
363
363
  try:
364
364
  # docker export yields paths without prefixed slashes, so we add them here
365
365
  # since the file is pretty big (~4MB for community, ~7MB for pro) we gzip it
366
- cmd = "docker export %s | tar -t | awk '{ print \"/\" $0 }' | gzip > %s" % (
367
- container_id,
368
- cache_file,
369
- )
366
+ cmd = f"docker export {container_id} | tar -t | awk '{{ print \"/\" $0 }}' | gzip > {cache_file}"
370
367
  run(cmd, shell=True)
371
368
  finally:
372
369
  container_client.remove_container(container_id)
@@ -65,7 +65,7 @@ class HookManager(PluginManager):
65
65
  fn_plugin(*args, **kwargs)
66
66
 
67
67
  def __str__(self):
68
- return "HookManager(%s)" % self.namespace
68
+ return f"HookManager({self.namespace})"
69
69
 
70
70
  def __repr__(self):
71
71
  return self.__str__()
@@ -91,7 +91,7 @@ class ShellScriptRunner(ScriptRunner):
91
91
  def run(self, path: str) -> None:
92
92
  exit_code = subprocess.call(args=[], executable=path)
93
93
  if exit_code != 0:
94
- raise OSError("Script %s returned a non-zero exit code %s" % (path, exit_code))
94
+ raise OSError(f"Script {path} returned a non-zero exit code {exit_code}")
95
95
 
96
96
 
97
97
  class PythonScriptRunner(ScriptRunner):
@@ -20,13 +20,13 @@ def print_runtime_information(in_docker: bool = False):
20
20
  if in_docker:
21
21
  try:
22
22
  container_name = get_main_container_name()
23
- print("LocalStack Docker container name: %s" % container_name)
23
+ print(f"LocalStack Docker container name: {container_name}")
24
24
  inspect_result = DOCKER_CLIENT.inspect_container(container_name)
25
25
  container_id = inspect_result["Id"]
26
- print("LocalStack Docker container id: %s" % container_id[:12])
26
+ print(f"LocalStack Docker container id: {container_id[:12]}")
27
27
  image_details = DOCKER_CLIENT.inspect_image(inspect_result["Image"])
28
28
  digests = image_details.get("RepoDigests") or ["Unavailable"]
29
- print("LocalStack Docker image sha: %s" % digests[0])
29
+ print(f"LocalStack Docker image sha: {digests[0]}")
30
30
  except ContainerException:
31
31
  print(
32
32
  "LocalStack Docker container info: Failed to inspect the LocalStack docker container. "
@@ -44,10 +44,10 @@ def print_runtime_information(in_docker: bool = False):
44
44
  )
45
45
 
46
46
  if config.LOCALSTACK_BUILD_DATE:
47
- print("LocalStack build date: %s" % config.LOCALSTACK_BUILD_DATE)
47
+ print(f"LocalStack build date: {config.LOCALSTACK_BUILD_DATE}")
48
48
 
49
49
  if config.LOCALSTACK_BUILD_GIT_HASH:
50
- print("LocalStack build git hash: %s" % config.LOCALSTACK_BUILD_GIT_HASH)
50
+ print(f"LocalStack build git hash: {config.LOCALSTACK_BUILD_GIT_HASH}")
51
51
 
52
52
  print()
53
53
 
@@ -367,10 +367,7 @@ def resolve_references(data: dict, rest_api_id, allow_recursive=True) -> dict:
367
367
 
368
368
  def path_based_url(api_id: str, stage_name: str, path: str) -> str:
369
369
  """Return URL for inbound API gateway for given API ID, stage name, and path"""
370
- pattern = "%s/restapis/{api_id}/{stage_name}/%s{path}" % (
371
- config.external_service_url(),
372
- PATH_USER_REQUEST,
373
- )
370
+ pattern = f"{config.external_service_url()}/restapis/{{api_id}}/{{stage_name}}/{PATH_USER_REQUEST}{{path}}"
374
371
  return pattern.format(api_id=api_id, stage_name=stage_name, path=path)
375
372
 
376
373
 
@@ -37,7 +37,7 @@ PATH_REGEX_TEST_INVOKE_API = r"^\/restapis\/([A-Za-z0-9_\-]+)\/resources\/([A-Za
37
37
 
38
38
  # regex path pattern for user requests, handles stages like $default
39
39
  PATH_REGEX_USER_REQUEST = (
40
- r"^/restapis/([A-Za-z0-9_\\-]+)(?:/([A-Za-z0-9\_($|%%24)\\-]+))?/%s/(.*)$" % PATH_USER_REQUEST
40
+ rf"^/restapis/([A-Za-z0-9_\\-]+)(?:/([A-Za-z0-9\_($|%24)\\-]+))?/{PATH_USER_REQUEST}/(.*)$"
41
41
  )
42
42
  # URL pattern for invocations
43
43
  HOST_REGEX_EXECUTE_API = r"(?:.*://)?([a-zA-Z0-9]+)(?:(-vpce-[^.]+))?\.execute-api\.(.*)"
@@ -375,12 +375,12 @@ def get_apigateway_path_for_resource(
375
375
  path_part = target_resource.get("pathPart", "")
376
376
  if path_suffix:
377
377
  if path_part:
378
- path_suffix = "%s/%s" % (path_part, path_suffix)
378
+ path_suffix = f"{path_part}/{path_suffix}"
379
379
  else:
380
380
  path_suffix = path_part
381
381
  parent_id = target_resource.get("parentId")
382
382
  if not parent_id:
383
- return "/%s" % path_suffix
383
+ return f"/{path_suffix}"
384
384
  return get_apigateway_path_for_resource(
385
385
  api_id,
386
386
  parent_id,
@@ -419,7 +419,7 @@ def get_resource_for_path(
419
419
  for api_path, details in path_map.items():
420
420
  api_path_regex = re.sub(r"{[^+]+\+}", r"[^\?#]+", api_path)
421
421
  api_path_regex = re.sub(r"{[^}]+}", r"[^/]+", api_path_regex)
422
- if re.match(r"^%s$" % api_path_regex, path):
422
+ if re.match(rf"^{api_path_regex}$", path):
423
423
  matches.append((api_path, details))
424
424
 
425
425
  # if there are no matches, it's not worth to proceed, bail here!
@@ -508,8 +508,7 @@ def connect_api_gateway_to_sqs(gateway_name, stage_name, queue_arn, path, accoun
508
508
  "integrations": [
509
509
  {
510
510
  "type": "AWS",
511
- "uri": "arn:%s:apigateway:%s:sqs:path/%s/%s"
512
- % (partition, sqs_region, sqs_account, queue_name),
511
+ "uri": f"arn:{partition}:apigateway:{sqs_region}:sqs:path/{sqs_account}/{queue_name}",
513
512
  "requestTemplates": {"application/json": template},
514
513
  "requestParameters": {
515
514
  "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'"
@@ -660,11 +659,11 @@ def set_api_id_stage_invocation_path(
660
659
  if path_match:
661
660
  api_id = path_match.group(1)
662
661
  stage = path_match.group(2)
663
- relative_path_w_query_params = "/%s" % path_match.group(3)
662
+ relative_path_w_query_params = f"/{path_match.group(3)}"
664
663
  elif host_match:
665
664
  api_id = extract_api_id_from_hostname_in_url(host_header)
666
665
  stage = path.strip("/").split("/")[0]
667
- relative_path_w_query_params = "/%s" % path.lstrip("/").partition("/")[2]
666
+ relative_path_w_query_params = "/{}".format(path.lstrip("/").partition("/")[2])
668
667
  elif test_invoke_match:
669
668
  stage = invocation_context.stage
670
669
  api_id = invocation_context.api_id
@@ -783,7 +783,7 @@ class HTTPIntegration(BackendIntegration):
783
783
  instances = client.list_instances(ServiceId=service_id)["Instances"]
784
784
  instance = (instances or [None])[0]
785
785
  if instance and instance.get("Id"):
786
- uri = "http://%s/%s" % (instance["Id"], invocation_path.lstrip("/"))
786
+ uri = "http://{}/{}".format(instance["Id"], invocation_path.lstrip("/"))
787
787
 
788
788
  # apply custom request template
789
789
  invocation_context.context = get_event_request_context(invocation_context)
@@ -977,8 +977,9 @@ class StepFunctionIntegration(BackendIntegration):
977
977
  headers={"Content-Type": APPLICATION_JSON},
978
978
  data=json.dumps(
979
979
  {
980
- "message": "StepFunctions execution %s failed with status '%s'"
981
- % (result["executionArn"], result_status)
980
+ "message": "StepFunctions execution {} failed with status '{}'".format(
981
+ result["executionArn"], result_status
982
+ )
982
983
  }
983
984
  ),
984
985
  )
@@ -280,7 +280,7 @@ def invoke_rest_api(invocation_context: ApiInvocationContext):
280
280
 
281
281
  extracted_path, resource = get_target_resource_details(invocation_context)
282
282
  if not resource:
283
- return make_error_response("Unable to find path %s" % invocation_context.path, 404)
283
+ return make_error_response(f"Unable to find path {invocation_context.path}", 404)
284
284
 
285
285
  # validate request
286
286
  validator = RequestValidator(invocation_context)
@@ -307,7 +307,7 @@ def invoke_rest_api(invocation_context: ApiInvocationContext):
307
307
  # default to returning CORS headers if this is an OPTIONS request
308
308
  return get_cors_response(headers)
309
309
  return make_error_response(
310
- "Unable to find integration for: %s %s (%s)" % (method, invocation_path, raw_path),
310
+ f"Unable to find integration for: {method} {invocation_path} ({raw_path})",
311
311
  404,
312
312
  )
313
313
 
@@ -3025,7 +3025,7 @@ def to_documentation_part_response_json(api_id, data):
3025
3025
 
3026
3026
 
3027
3027
  def to_base_mapping_response_json(domain_name, base_path, data):
3028
- self_link = "/domainnames/%s/basepathmappings/%s" % (domain_name, base_path)
3028
+ self_link = f"/domainnames/{domain_name}/basepathmappings/{base_path}"
3029
3029
  result = to_response_json("basepathmapping", data, self_link=self_link)
3030
3030
  result = select_from_typed_dict(BasePathMapping, result)
3031
3031
  return result
@@ -3061,9 +3061,9 @@ def to_response_json(model_type, data, api_id=None, self_link=None, id_attr=None
3061
3061
  id_attr = id_attr or "id"
3062
3062
  result = deepcopy(data)
3063
3063
  if not self_link:
3064
- self_link = "/%ss/%s" % (model_type, data[id_attr])
3064
+ self_link = f"/{model_type}s/{data[id_attr]}"
3065
3065
  if api_id:
3066
- self_link = "/restapis/%s/%s" % (api_id, self_link)
3066
+ self_link = f"/restapis/{api_id}/{self_link}"
3067
3067
  # TODO: check if this is still required - "_links" are listed in the sample responses in the docs, but
3068
3068
  # recent parity tests indicate that this field is not returned by real AWS...
3069
3069
  # https://docs.aws.amazon.com/apigateway/latest/api/API_GetAuthorizers.html#API_GetAuthorizers_Example_1_Response
@@ -3075,7 +3075,7 @@ def to_response_json(model_type, data, api_id=None, self_link=None, id_attr=None
3075
3075
  "name": model_type,
3076
3076
  "templated": True,
3077
3077
  }
3078
- result["_links"]["%s:delete" % model_type] = {"href": self_link}
3078
+ result["_links"][f"{model_type}:delete"] = {"href": self_link}
3079
3079
  return result
3080
3080
 
3081
3081
 
@@ -72,7 +72,7 @@ class ApiGatewayResourceProvider(ResourceProvider[ApiGatewayResourceProperties])
72
72
  root_resource = ([r for r in resources if r["path"] == "/"] or [None])[0]
73
73
  if not root_resource:
74
74
  raise Exception(
75
- "Unable to find root resource for REST API %s" % params["restApiId"]
75
+ "Unable to find root resource for REST API {}".format(params["restApiId"])
76
76
  )
77
77
  params["parentId"] = root_resource["id"]
78
78
  response = apigw.create_resource(**params)
@@ -77,9 +77,7 @@ def get_remote_template_body(url: str) -> str:
77
77
  result = client.get_object(Bucket=parts[0], Key=parts[2])
78
78
  body = to_str(result["Body"].read())
79
79
  return body
80
- raise RuntimeError(
81
- "Unable to fetch template body (code %s) from URL %s" % (status_code, url)
82
- )
80
+ raise RuntimeError(f"Unable to fetch template body (code {status_code}) from URL {url}")
83
81
  else:
84
82
  raise RuntimeError(
85
83
  f"Bad status code from fetching template from url '{url}' ({status_code})",
@@ -112,11 +110,9 @@ def get_template_body(req_data: dict) -> str:
112
110
  result = client.get_object(Bucket=parts[0], Key=parts[2])
113
111
  body = to_str(result["Body"].read())
114
112
  return body
115
- raise Exception(
116
- "Unable to fetch template body (code %s) from URL %s" % (status_code, url)
117
- )
113
+ raise Exception(f"Unable to fetch template body (code {status_code}) from URL {url}")
118
114
  return to_str(response.content)
119
- raise Exception("Unable to get template body from input: %s" % req_data)
115
+ raise Exception(f"Unable to get template body from input: {req_data}")
120
116
 
121
117
 
122
118
  def is_local_service_url(url: str) -> bool:
@@ -127,7 +123,7 @@ def is_local_service_url(url: str) -> bool:
127
123
  constants.LOCALHOST_HOSTNAME,
128
124
  localstack_host().host,
129
125
  )
130
- if any(re.match(r"^[^:]+://[^:/]*%s([:/]|$)" % host, url) for host in candidates):
126
+ if any(re.match(rf"^[^:]+://[^:/]*{host}([:/]|$)", url) for host in candidates):
131
127
  return True
132
128
  host = url.split("://")[-1].split("/")[0]
133
129
  return "localhost" in host
@@ -59,7 +59,7 @@ def convert_types(obj, types):
59
59
  def recurse(o, path):
60
60
  if isinstance(o, dict):
61
61
  for k, v in dict(o).items():
62
- key_path = "%s%s" % (path or ".", k)
62
+ key_path = "{}{}".format(path or ".", k)
63
63
  if key in [k, key_path]:
64
64
  o[k] = type_class(v)
65
65
  return o
@@ -360,8 +360,7 @@ class Stack:
360
360
  resource = resource_map.get(resource_id)
361
361
  if not resource:
362
362
  raise Exception(
363
- 'Unable to find details for resource "%s" in stack "%s"'
364
- % (resource_id, self.stack_name)
363
+ f'Unable to find details for resource "{resource_id}" in stack "{self.stack_name}"'
365
364
  )
366
365
  return resource
367
366
 
@@ -329,7 +329,7 @@ def _resolve_refs_recursively(
329
329
  account_id, region_name, stack_name, resources, parameters, value["Ref"]
330
330
  )
331
331
  if ref is None:
332
- msg = 'Unable to resolve Ref for resource "%s" (yet)' % value["Ref"]
332
+ msg = 'Unable to resolve Ref for resource "{}" (yet)'.format(value["Ref"])
333
333
  LOG.debug("%s - %s", msg, resources.get(value["Ref"]) or set(resources.keys()))
334
334
 
335
335
  raise DependencyNotYetSatisfied(resource_ids=value["Ref"], message=msg)
@@ -450,7 +450,7 @@ def _resolve_refs_recursively(
450
450
  raise DependencyNotYetSatisfied(
451
451
  resource_ids=key, message=f"Could not resolve {val} to terminal value type"
452
452
  )
453
- result = result.replace("${%s}" % key, str(resolved_val))
453
+ result = result.replace(f"${{{key}}}", str(resolved_val))
454
454
 
455
455
  # resolve placeholders
456
456
  result = resolve_placeholders_in_string(
@@ -146,7 +146,7 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
146
146
  if not location or not location.startswith("s3://"):
147
147
  raise FailedTransformationException(
148
148
  transformation=INCLUDE_TRANSFORM,
149
- message="Unexpected Location parameter for AWS::Include transformer: %s" % location,
149
+ message=f"Unexpected Location parameter for AWS::Include transformer: {location}",
150
150
  )
151
151
 
152
152
  s3_client = connect_to(
@@ -158,7 +158,7 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
158
158
  except ClientError:
159
159
  raise FailedTransformationException(
160
160
  transformation=INCLUDE_TRANSFORM,
161
- message="Error downloading S3 object '%s/%s'" % (bucket, path),
161
+ message=f"Error downloading S3 object '{bucket}/{path}'",
162
162
  )
163
163
  try:
164
164
  template_to_include = parse_template(content)
@@ -159,7 +159,7 @@ def find_stack_instance(stack_set: StackSet, account: str, region: str):
159
159
 
160
160
  def stack_not_found_error(stack_name: str):
161
161
  # FIXME
162
- raise ValidationError("Stack with id %s does not exist" % stack_name)
162
+ raise ValidationError(f"Stack with id {stack_name} does not exist")
163
163
 
164
164
 
165
165
  def not_found_error(message: str):
@@ -305,7 +305,7 @@ class CloudformationProvider(CloudformationApi):
305
305
  deployer.deploy_stack()
306
306
  except Exception as e:
307
307
  stack.set_stack_status("CREATE_FAILED")
308
- msg = 'Unable to create stack "%s": %s' % (stack.stack_name, e)
308
+ msg = f'Unable to create stack "{stack.stack_name}": {e}'
309
309
  LOG.error("%s", exc_info=LOG.isEnabledFor(logging.DEBUG))
310
310
  raise ValidationError(msg) from e
311
311
 
@@ -13,7 +13,7 @@ class DependencyNotYetSatisfied(Exception):
13
13
  """Exception indicating that a resource dependency is not (yet) deployed/available."""
14
14
 
15
15
  def __init__(self, resource_ids, message=None):
16
- message = message or "Unresolved dependencies: %s" % resource_ids
16
+ message = message or f"Unresolved dependencies: {resource_ids}"
17
17
  super().__init__(message)
18
18
  resource_ids = resource_ids if isinstance(resource_ids, list) else [resource_ids]
19
19
  self.resource_ids = resource_ids
@@ -162,7 +162,7 @@ class DynamodbServer(Server):
162
162
  cmd = [
163
163
  "java",
164
164
  *self._get_java_vm_options(),
165
- "-Xmx%s" % self.heap_size,
165
+ f"-Xmx{self.heap_size}",
166
166
  f"-javaagent:{dynamodblocal_package.get_installer().get_ddb_agent_jar_path()}",
167
167
  f"-Djava.library.path={self.library_path}",
168
168
  "-jar",
@@ -72,7 +72,7 @@ def start_component(
72
72
  default_port=constants.DEFAULT_PORT_EDGE,
73
73
  ),
74
74
  )
75
- raise Exception("Unexpected component name '%s' received during start up" % component)
75
+ raise Exception(f"Unexpected component name '{component}' received during start up")
76
76
 
77
77
 
78
78
  def start_proxy(
@@ -206,12 +206,12 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
206
206
  raise ValueError("If the value is greater than 1, the unit must be plural")
207
207
 
208
208
  if "minute" in unit:
209
- return "*/%s * * * *" % value
209
+ return f"*/{value} * * * *"
210
210
  if "hour" in unit:
211
- return "0 */%s * * *" % value
211
+ return f"0 */{value} * * *"
212
212
  if "day" in unit:
213
- return "0 0 */%s * *" % value
214
- raise ValueError("Unable to parse events schedule expression: %s" % schedule)
213
+ return f"0 0 */{value} * *"
214
+ raise ValueError(f"Unable to parse events schedule expression: {schedule}")
215
215
  return schedule
216
216
 
217
217
  @staticmethod
@@ -374,7 +374,7 @@ def _dump_events_to_files(events_with_added_uuid):
374
374
  for event in events_with_added_uuid:
375
375
  target = os.path.join(
376
376
  _get_events_tmp_dir(),
377
- "%s_%s" % (current_time_millis, event["uuid"]),
377
+ "{}_{}".format(current_time_millis, event["uuid"]),
378
378
  )
379
379
  save_file(target, json.dumps(event["event"]))
380
380
  except Exception as e:
@@ -49,7 +49,7 @@ def find_stream_for_consumer(consumer_arn):
49
49
  for cons in kinesis.list_stream_consumers(StreamARN=stream_arn)["Consumers"]:
50
50
  if cons["ConsumerARN"] == consumer_arn:
51
51
  return stream_name
52
- raise Exception("Unable to find stream for stream consumer %s" % consumer_arn)
52
+ raise Exception(f"Unable to find stream for stream consumer {consumer_arn}")
53
53
 
54
54
 
55
55
  class KinesisProvider(KinesisApi, ServiceLifecycleHook):
@@ -286,19 +286,19 @@ class ServiceManager:
286
286
  container = self.get_service_container(name)
287
287
 
288
288
  if not container:
289
- raise ValueError("no such service %s" % name)
289
+ raise ValueError(f"no such service {name}")
290
290
 
291
291
  if container.state == ServiceState.STARTING:
292
292
  if not poll_condition(lambda: container.state != ServiceState.STARTING, timeout=30):
293
- raise TimeoutError("gave up waiting for service %s to start" % name)
293
+ raise TimeoutError(f"gave up waiting for service {name} to start")
294
294
 
295
295
  if container.state == ServiceState.STOPPING:
296
296
  if not poll_condition(lambda: container.state == ServiceState.STOPPED, timeout=30):
297
- raise TimeoutError("gave up waiting for service %s to stop" % name)
297
+ raise TimeoutError(f"gave up waiting for service {name} to stop")
298
298
 
299
299
  with container.lock:
300
300
  if container.state == ServiceState.DISABLED:
301
- raise ServiceDisabled("service %s is disabled" % name)
301
+ raise ServiceDisabled(f"service {name} is disabled")
302
302
 
303
303
  if container.state == ServiceState.RUNNING:
304
304
  return container.service
@@ -314,7 +314,7 @@ class ServiceManager:
314
314
  raise container.errors[-1]
315
315
 
316
316
  raise ServiceStateException(
317
- "service %s is not ready (%s) and could not be started" % (name, container.state)
317
+ f"service {name} is not ready ({container.state}) and could not be started"
318
318
  )
319
319
 
320
320
  # legacy map compatibility
@@ -692,7 +692,7 @@ def check_service_health(api, expect_shutdown=False):
692
692
  LOG.warning('Service "%s" not yet available, retrying...', api)
693
693
  else:
694
694
  LOG.warning('Service "%s" still shutting down, retrying...', api)
695
- raise Exception("Service check failed for api: %s" % api)
695
+ raise Exception(f"Service check failed for api: {api}")
696
696
 
697
697
 
698
698
  @hooks.on_infra_start(should_load=lambda: config.EAGER_SERVICE_LOADING)
@@ -366,7 +366,7 @@ class SsmProvider(SsmApi, ABC):
366
366
  param_name = param_name.strip("/")
367
367
  param_name = param_name.replace("//", "/")
368
368
  if "/" in param_name:
369
- param_name = "/%s" % param_name
369
+ param_name = f"/{param_name}"
370
370
  return param_name
371
371
 
372
372
  @staticmethod