From 3523d09f624dc3934138c8a5994ae1994f8beb62 Mon Sep 17 00:00:00 2001 From: SamRemis Date: Wed, 13 Nov 2024 14:10:17 -0500 Subject: [PATCH] initial commit --- botocore/endpoint_provider.py | 6 ++- botocore/model.py | 4 ++ botocore/regions.py | 15 ++++++++ botocore/utils.py | 2 +- .../endpoints/test-cases/array-index.json | 24 ++++++++++++ .../endpoints/valid-rules/array-index.json | 37 +++++++++++++++++++ tests/unit/test_endpoint_provider.py | 4 ++ 7 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 tests/unit/data/endpoints/test-cases/array-index.json create mode 100644 tests/unit/data/endpoints/valid-rules/array-index.json diff --git a/botocore/endpoint_provider.py b/botocore/endpoint_provider.py index 9439086c53..360e452532 100644 --- a/botocore/endpoint_provider.py +++ b/botocore/endpoint_provider.py @@ -42,7 +42,7 @@ logger = logging.getLogger(__name__) TEMPLATE_STRING_RE = re.compile(r"\{[a-zA-Z#]+\}") -GET_ATTR_RE = re.compile(r"(\w+)\[(\d+)\]") +GET_ATTR_RE = re.compile(r"(\w*)\[(\d+)\]") VALID_HOST_LABEL_RE = re.compile( r"^(?!-)[a-zA-Z\d-]{1,63}(?= len(value): return None return value[index] @@ -576,6 +577,7 @@ class ParameterType(Enum): string = str boolean = bool + stringarray = tuple class ParameterDefinition: diff --git a/botocore/model.py b/botocore/model.py index 70d20f8cca..efd15e90e7 100644 --- a/botocore/model.py +++ b/botocore/model.py @@ -624,6 +624,10 @@ def context_parameters(self): and 'name' in shape.metadata['contextParam'] ] + @CachedProperty + def operation_context_parameters(self): + return self._operation_model.get('operationContextParams', []) + @CachedProperty def request_compression(self): return self._operation_model.get('requestcompression') diff --git a/botocore/regions.py b/botocore/regions.py index 4c23737270..9f1a641873 100644 --- a/botocore/regions.py +++ b/botocore/regions.py @@ -20,6 +20,8 @@ import copy import logging import re +import jmespath + from enum import Enum from botocore import UNSIGNED, xform_name @@ -578,6 +580,11 @@ def _resolve_param_from_context( ) if dynamic is not None: return dynamic + operation_context_params = self._resolve_param_as_operation_context_param( + param_name, operation_model, call_args + ) + if operation_context_params is not None: + return operation_context_params return self._resolve_param_as_client_context_param(param_name) def _resolve_param_as_static_context_param( @@ -600,6 +607,14 @@ def _resolve_param_as_client_context_param(self, param_name): client_ctx_varname = client_ctx_params[param_name] return self._client_context.get(client_ctx_varname) + def _resolve_param_as_operation_context_param(self, param_name, operation_model, + call_args): + operation_ctx_params = operation_model.operation_context_parameters + if param_name in operation_ctx_params: + #TODO confirm this will always exist + path = operation_ctx_params[param_name]['path'] + return tuple(jmespath.search(path, call_args)) + def _resolve_param_as_builtin(self, builtin_name, builtins): if builtin_name not in EndpointResolverBuiltins.__members__.values(): raise UnknownEndpointResolutionBuiltInName(name=builtin_name) diff --git a/botocore/utils.py b/botocore/utils.py index 314d30516d..88f5c11806 100644 --- a/botocore/utils.py +++ b/botocore/utils.py @@ -1479,7 +1479,7 @@ def lru_cache_weakref(*cache_args, **cache_kwargs): functools implementation which offers ``max_size`` and ``typed`` properties. lru_cache is a global cache even when used on a method. The cache's - reference to ``self`` will prevent garbace collection of the object. This + reference to ``self`` will prevent garbage collection of the object. This wrapper around functools.lru_cache replaces the reference to ``self`` with a weak reference to not interfere with garbage collection. """ diff --git a/tests/unit/data/endpoints/test-cases/array-index.json b/tests/unit/data/endpoints/test-cases/array-index.json new file mode 100644 index 0000000000..bb90e1cb6f --- /dev/null +++ b/tests/unit/data/endpoints/test-cases/array-index.json @@ -0,0 +1,24 @@ +{ + "version": "1.0", + "testCases": [ + { + "documentation": "Access an array index at index 0", + "params": { + "Input": ["first index"] + }, + "expect": { + "endpoint": { + "url": "https://www.example.com", + "headers": { + "x-uri": [ + "https://www.example.com" + ], + "x-arn-region": [ + "us-east-2" + ] + } + } + } + } + ] +} diff --git a/tests/unit/data/endpoints/valid-rules/array-index.json b/tests/unit/data/endpoints/valid-rules/array-index.json new file mode 100644 index 0000000000..3bb2c464ef --- /dev/null +++ b/tests/unit/data/endpoints/valid-rules/array-index.json @@ -0,0 +1,37 @@ +{ + "version": "1.3", + "parameters": { + "ResourceList": { + "type": "stringArray" + } + }, + "rules": [ + { + "documentation": "Array is set, retrieve index 0", + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "ResourceList" + } + ] + }, + { + "fn": "getAttr", + "argv": [ + { + "ref": "bucketArn" + }, + "resourceId[2]" + ], + "assign": "outpostId" + } + ], + "endpoint": { + "url": "https://{firstResourceId}.example.com" + }, + "type": "endpoint" + } + ] +} diff --git a/tests/unit/test_endpoint_provider.py b/tests/unit/test_endpoint_provider.py index c1f82ace2e..a01ac9c3cd 100644 --- a/tests/unit/test_endpoint_provider.py +++ b/tests/unit/test_endpoint_provider.py @@ -137,6 +137,7 @@ def endpoint_rule(): def ruleset_testcases(): filenames = [ + "array-index", "aws-region", "default-values", "eventbridge", @@ -161,6 +162,9 @@ def ruleset_testcases(): for test in tests["testCases"]: input_params = test["params"] + for key, value in input_params.items(): + if type(value) == list: + input_params[key] = tuple(value) expected_object = test["expect"] if "error" in expected_object: error_cases.append(