Skip to content

Commit 92a3b19

Browse files
authoredDec 10, 2024
Merge from aws/aws-sam-cli/develop
2 parents 6130371 + 400ed03 commit 92a3b19

File tree

15 files changed

+965
-415
lines changed

15 files changed

+965
-415
lines changed
 

‎.github/workflows/update-reproducibles.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
python: 3.11
2323
target: update-reproducible-linux-reqs
2424
- os: macos-latest
25-
python: 3.8
25+
python: 3.11
2626
target: update-reproducible-mac-reqs
2727
- os: windows-latest
2828
python: 3.12

‎Makefile

+4-6
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,17 @@ schema:
5858
# Verifications to run before sending a pull request
5959
pr: init dev schema black-check
6060

61-
# (jfuss) We updated to have two requirement files, one for mac and one for linux. This
62-
# is meant to be a short term fix when upgrading the Linux installer to be python3.11 from
63-
# python3.7. Having different requirements is not ideal but this allows us to isolate changes
64-
# giving us the ability to roll out upgrade to Linux first. When we update the MacOS installer
65-
# we can move to a single file again.
61+
# lucashuy: Linux and MacOS are on the same Python version,
62+
# however we should follow up in a different change
63+
# to consider combining these files again
6664
update-reproducible-linux-reqs:
6765
python3.11 -m venv venv-update-reproducible-linux
6866
venv-update-reproducible-linux/bin/pip install --upgrade pip-tools pip
6967
venv-update-reproducible-linux/bin/pip install -r requirements/base.txt
7068
venv-update-reproducible-linux/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-linux.txt
7169

7270
update-reproducible-mac-reqs:
73-
python3.8 -m venv venv-update-reproducible-mac
71+
python3.11 -m venv venv-update-reproducible-mac
7472
venv-update-reproducible-mac/bin/pip install --upgrade pip-tools pip
7573
venv-update-reproducible-mac/bin/pip install -r requirements/base.txt
7674
venv-update-reproducible-mac/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-mac.txt

‎installer/pyinstaller/build-mac.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ if [ "$openssl_version" = "" ]; then
3434
fi
3535

3636
if [ "$python_version" = "" ]; then
37-
python_version="3.8.20";
37+
python_version="3.11.10";
3838
fi
3939

4040
if ! [ "$build_binary_name" = "" ]; then
@@ -100,7 +100,7 @@ sudo make -j8 install
100100
cd ..
101101

102102
echo "Installing Python Libraries"
103-
/usr/local/bin/python3.8 -m venv venv
103+
/usr/local/bin/python3.11 -m venv venv
104104
./venv/bin/pip install --upgrade pip
105105
./venv/bin/pip install -r src/requirements/reproducible-mac.txt
106106

‎requirements/base.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
chevron~=0.12
22
click~=8.1
3-
Flask<3.1
3+
Flask<3.2
44
boto3>=1.29.2,<2
55
jmespath~=1.0.1
66
ruamel_yaml~=0.18.6
@@ -11,7 +11,7 @@ aws-sam-translator==1.94.0
1111
docker~=7.1.0
1212
dateparser~=1.2
1313
requests~=2.32.3
14-
aws_lambda_builders==1.52.0
14+
aws_lambda_builders==1.53.0
1515
tomlkit==0.13.2
1616
# NOTE: For supporting watchdog in Python3.8, version is pinned to 4.0.2 as
1717
# version 5.0.2 introduced some breaking changes for versions > Python3.8
@@ -31,7 +31,7 @@ regex!=2021.10.8
3131
tzlocal==5.2
3232

3333
#Adding cfn-lint dependency for SAM validate
34-
cfn-lint~=1.19.0
34+
cfn-lint~=1.20.1
3535

3636
# Type checking boto3 objects
3737
boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray,sqs,kinesis]==1.35.63

‎requirements/dev.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-r pre-dev.txt
22

3-
coverage==7.6.7; python_version>="3.9"
3+
coverage==7.6.8; python_version>="3.9"
44
coverage==7.6.1; python_version<"3.9"
55
pytest-cov==6.0.0; python_version>="3.9"
66
pytest-cov==5.0.0; python_version<"3.9"
@@ -10,15 +10,15 @@ pytest-cov==5.0.0; python_version<"3.9"
1010
# mypy adds new rules in new minor versions, which could cause our PR check to fail
1111
# here we fix its version and upgrade it manually in the future
1212
mypy==1.13.0
13-
types-pywin32==308.0.0.20241029
13+
types-pywin32==308.0.0.20241128
1414
types-PyYAML==6.0.12.20240917
1515
types-chevron==0.14.2.20240310
1616
types-psutil==6.1.0.20241102
17-
types-setuptools==75.5.0.20241116
17+
types-setuptools==75.6.0.20241126
1818
types-Pygments==2.18.0.20240506
1919
types-colorama==0.4.15.20240311
2020
types-dateparser==1.2.0.20240420
21-
types-docutils==0.21.0.20241005
21+
types-docutils==0.21.0.20241128
2222
types-jsonschema==4.23.0.20240813
2323
types-pyOpenSSL==24.1.0.20240722
2424
# lucashuy: pin `types-request` based on the Python version since newer versions of

‎requirements/pre-dev.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ruff==0.7.4
1+
ruff==0.8.0

‎requirements/reproducible-linux.txt

+125-114
Large diffs are not rendered by default.

‎requirements/reproducible-mac.txt

+123-153
Large diffs are not rendered by default.

‎requirements/reproducible-win.txt

+125-114
Large diffs are not rendered by default.

‎samcli/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
SAM CLI version
33
"""
44

5-
__version__ = "1.131.0"
5+
__version__ = "1.132.0"

‎samcli/cli/types.py

+116-14
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
LOG = logging.getLogger(__name__)
1818

1919

20-
def _generate_match_regex(match_pattern, delim):
20+
def _generate_match_regex(match_pattern, delim=None):
2121
"""
2222
Creates a regex string based on a match pattern (also a regex) that is to be
2323
run on a string (which may contain escaped quotes) that is separated by delimiters.
@@ -32,13 +32,13 @@ def _generate_match_regex(match_pattern, delim):
3232
str: regex expression
3333
3434
"""
35+
result = f"""(\\"(?:\\\\{match_pattern}|[^\\"\\\\]+)*\\"|""" + f"""\'(?:\\\\{match_pattern}|[^\'\\\\]+)*\'"""
3536

36-
# Non capturing groups reduces duplicates in groups, but does not reduce matches.
37-
return (
38-
f"""(\\"(?:\\\\{match_pattern}|[^\\"\\\\]+)*\\"|"""
39-
+ f"""\'(?:\\\\{match_pattern}|[^\'\\\\]+)*\'|"""
40-
+ f"""(?:\\\\{match_pattern}|[^{delim}\\"\\\\]+)+)"""
41-
)
37+
if delim is not None:
38+
# Non capturing groups reduces duplicates in groups, but does not reduce matches.
39+
return result + f"""|(?:\\\\{match_pattern}|[^{delim}\\"\\\\]+)+)"""
40+
else:
41+
return result + ")"
4242

4343

4444
def _unquote_wrapped_quotes(value):
@@ -194,6 +194,7 @@ def __init__(self, multiple_values_per_key=False):
194194
TAG_REGEX = '[A-Za-z0-9\\"_:\\.\\/\\+-\\@=]'
195195

196196
_pattern = r"{tag}={tag}".format(tag=_generate_match_regex(match_pattern=TAG_REGEX, delim=" "))
197+
_quoted_pattern = _generate_match_regex(match_pattern=TAG_REGEX)
197198

198199
name = "string,list"
199200

@@ -222,13 +223,7 @@ def convert(self, value, param, ctx):
222223
for k in tags:
223224
self._add_value(result, _unquote_wrapped_quotes(k), _unquote_wrapped_quotes(tags[k]))
224225
else:
225-
groups = re.findall(self._pattern, val)
226-
227-
if not groups:
228-
fail = True
229-
for group in groups:
230-
key, v = group
231-
self._add_value(result, _unquote_wrapped_quotes(key), _unquote_wrapped_quotes(v))
226+
fail = not self._parse_key_value_pair(result, val)
232227

233228
if fail:
234229
return self.fail(
@@ -239,6 +234,66 @@ def convert(self, value, param, ctx):
239234

240235
return result
241236

237+
def _parse_key_value_pair(self, result: dict, key_value_string: str):
238+
"""
239+
This method processes a string in the format "'key1'='value1','key2'='value2'",
240+
where spaces may exist within keys or values.
241+
242+
To optimize performance, the parsing is divided into two stages:
243+
244+
Stage 1: Optimized Parsing
245+
1. Identify quoted strings containing spaces within values.
246+
2. Temporarily replace spaces in these strings with a placeholder (e.g., "_").
247+
3. Use a fast, standard parser to extract key-value pairs, as no spaces are expected.
248+
4. Restore original spaces in the extracted key-value pairs.
249+
250+
Stage 2: Fallback Parsing
251+
If Stage 1 fails to parse the string correctly,run against a comprehensive regex pattern
252+
{tag}={tag}) to parse the entire string.
253+
254+
Parameters
255+
----------
256+
result: result dict
257+
key_value_string: string to parse
258+
259+
Returns
260+
-------
261+
boolean - parse result
262+
"""
263+
parse_result = True
264+
265+
# Unquote an entire string
266+
modified_val = _unquote_wrapped_quotes(key_value_string)
267+
268+
# Looking for a quote strings that contain spaces and proceed to replace them
269+
quoted_strings_with_spaces = re.findall(self._quoted_pattern, modified_val)
270+
quoted_strings_with_spaces_objects = [
271+
TextWithSpaces(str_with_spaces) for str_with_spaces in quoted_strings_with_spaces
272+
]
273+
for s, replacement in zip(quoted_strings_with_spaces, quoted_strings_with_spaces_objects):
274+
modified_val = modified_val.replace(s, replacement.replace_spaces())
275+
276+
# Use default parser to parse key=value
277+
tags = self._multiple_space_separated_key_value_parser(modified_val)
278+
if tags is not None:
279+
for key, value in tags.items():
280+
new_value = value
281+
text_objects = [obj for obj in quoted_strings_with_spaces_objects if obj.modified_text == value]
282+
if len(text_objects) > 0:
283+
new_value = text_objects[0].restore_spaces()
284+
self._add_value(result, _unquote_wrapped_quotes(key), _unquote_wrapped_quotes(new_value))
285+
else:
286+
# Otherwise, fall back to the original mechanism.
287+
groups = re.findall(self._pattern, key_value_string)
288+
289+
if not groups:
290+
parse_result = False
291+
for group in groups:
292+
key, v = group
293+
self._add_value(result, _unquote_wrapped_quotes(key), _unquote_wrapped_quotes(v))
294+
295+
return parse_result
296+
242297
def _add_value(self, result: dict, key: str, new_value: str):
243298
"""
244299
Add a given value to a given key in the result map.
@@ -286,6 +341,22 @@ def _space_separated_key_value_parser(tag_value):
286341
tags_dict = {**tags_dict, **parsed_tag}
287342
return True, tags_dict
288343

344+
@staticmethod
345+
def _multiple_space_separated_key_value_parser(tag_value):
346+
"""
347+
Method to parse space separated `Key1=Value1 Key2=Value2` type tags without using regex.
348+
Parameters
349+
----------
350+
tag_value
351+
"""
352+
tags_dict = {}
353+
for value in tag_value.split():
354+
parsed, parsed_tag = CfnTags._standard_key_value_parser(value)
355+
if not parsed:
356+
return None
357+
tags_dict.update(parsed_tag)
358+
return tags_dict
359+
289360

290361
class SigningProfilesOptionType(click.ParamType):
291362
"""
@@ -560,3 +631,34 @@ def convert(
560631
)
561632

562633
return {resource_id: [excluded_path]}
634+
635+
636+
class TextWithSpaces:
637+
def __init__(self, text) -> None:
638+
self.text = text
639+
self.modified_text = text
640+
self.space_positions = [] # type: List[int]
641+
642+
def replace_spaces(self, replacement="_"):
643+
"""
644+
Replace spaces in a text with a replacement together with its original locations.
645+
Input: "test 1"
646+
Output: "test_1" [4]
647+
"""
648+
self.space_positions = [i for i, char in enumerate(self.text) if char == " "]
649+
self.modified_text = self.text.replace(" ", replacement)
650+
651+
return self.modified_text
652+
653+
def restore_spaces(self):
654+
"""
655+
Restore spaces in a text from a original space locations.
656+
Input: "test_1" [4]
657+
Output: "test 1"
658+
"""
659+
text_list = list(self.modified_text)
660+
661+
for pos in self.space_positions:
662+
text_list[pos] = " "
663+
664+
return "".join(text_list)

‎samcli/commands/_utils/options.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -849,8 +849,10 @@ def resolve_image_repos_option(f):
849849

850850
def use_container_build_click_option():
851851
return click.option(
852-
"--use-container",
852+
"--use-container/--no-use-container",
853853
"-u",
854+
required=False,
855+
default=False,
854856
is_flag=True,
855857
help="Build functions within an AWS Lambda-like container.",
856858
)

‎samcli/runtime_config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"app_template_repo_commit": "e9255c4b848b523ca903e5ee0fbd28d52f2a4c4e"
2+
"app_template_repo_commit": "4801f61b2288fa85f2e06cf5c1a8d8dbe22094cb"
33
}

‎tests/unit/cli/test_types.py

+8
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ def test_must_fail_on_invalid_format(self, input):
238238
["stage=int", "company:application=awesome-service", "company:department=engineering"],
239239
{"stage": "int", "company:application": "awesome-service", "company:department": "engineering"},
240240
),
241+
# input as string with multiple key-values including spaces
242+
(('tag1="son of anton" tag2="company abc"',), {"tag1": "son of anton", "tag2": "company abc"}),
243+
(('tag1="son of anton" tag2="company abc"',), {"tag1": "son of anton", "tag2": "company abc"}),
244+
(('\'tag1="son of anton" tag2="company abc"\'',), {"tag1": "son of anton", "tag2": "company abc"}),
245+
(
246+
('tag1="son of anton" tag2="company abc" tag:3="dummy tag"',),
247+
{"tag1": "son of anton", "tag2": "company abc", "tag:3": "dummy tag"},
248+
),
241249
]
242250
)
243251
def test_successful_parsing(self, input, expected):

0 commit comments

Comments
 (0)
Failed to load comments.