Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ff5fa33
feat: Support EndpointAccessMode property for AWS::Serverless::Api (#…
wandora58 Mar 30, 2026
2445bb1
chore: drop Python 3.9 support for running SAM, add Python 3.12, 3.13…
roger-zhangg Mar 30, 2026
9feaf7d
fix: restrict cfn-lint schema update to us-east-1 to avoid pickle err…
licjun Apr 2, 2026
75dc369
chore(schema): update (#3904)
github-actions[bot] Apr 8, 2026
d4c254c
Merge remote-tracking branch 'origin/develop' into tmp/1775691083/main
licjun Apr 9, 2026
10fccbc
chore: migrate WebSocket API files to Python 3.10+ type syntax
licjun Apr 9, 2026
ee02aac
chore: fix Dict type hint in apigatewayv2.py for Python 3.10+
licjun Apr 9, 2026
deba015
chore: fix Optional/Dict/List type hints in sam_resources.py for Pyth…
licjun Apr 9, 2026
7c305a8
chore: fix WebSocketApi casing in Globals schema
licjun Apr 9, 2026
8e52ec3
chore: apply black formatting to websocket_api_generator.py
licjun Apr 9, 2026
6dfc469
chore: update test expected output to include WebSocketApi in Globals
licjun Apr 9, 2026
700de74
Merge pull request #3907 from aws/tmp/1775691083/main
reedham-aws Apr 9, 2026
4ffd7a7
fix: pin closed-issue-message to hash (#3905)
reedham-aws Apr 9, 2026
310934b
refactor: improve HttpApi authorizer validation (#3899)
vicheey Apr 13, 2026
5d0a1be
test: add condition to run RestApi's SecurityPolicy tests (#3908)
valerena Apr 14, 2026
3a27f7b
refactor(test): Pre-create MSK/MQ in companion stack for testing (#3909)
vicheey Apr 15, 2026
ea9b220
fix: add cfn-lint ignore rules for new format validation checks (#3915)
roger-zhangg Apr 21, 2026
6689aa3
feat: fully support Python 3.14 (#3913)
valerena Apr 22, 2026
6b6e8ce
chore(tests): use valid ARN formats in test fixtures to comply with c…
roger-zhangg Apr 22, 2026
da99012
fix: update companion stack to use `mq.m7i.large` for regions that do…
vicheey Apr 23, 2026
b4aabe1
chore(schema): update (#3918)
github-actions[bot] Apr 28, 2026
0656170
perf: use list join instead of string concatenation in loop (#3829)
akshatsinha0 Apr 29, 2026
87601ba
fix: surface user-facing errors for malformed Fn::If on Role and Code…
roger-zhangg Apr 29, 2026
aadb050
fix: handle intrinsic functions in WebSocket API StageName property (…
licjun May 6, 2026
d4975fa
chore: bump version to 1.110.0
aws-sam-cli-bot May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ jobs:
os:
- ubuntu-latest
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/close_issue_message.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
permissions:
issues: write
steps:
- uses: aws-actions/closed-issue-message@v2
- uses: aws-actions/closed-issue-message@10aaf6366131b673a7c8b7742f8b3849f1d44f18 # v2
with:
# These inputs are both required
repo-token: "${{ secrets.GITHUB_TOKEN }}"
Expand Down
34 changes: 18 additions & 16 deletions DEVELOPMENT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Environment setup
-----------------
### 1. Install Python versions

Our officially supported Python versions are 3.8, 3.9 and 3.10.
Our officially supported Python versions are 3.10, 3.11, 3.12, 3.13, 3.14
Our CI/CD pipeline is setup to run unit tests against Python 3 versions. Make sure you test it before sending a Pull Request.
See [Unit testing with multiple Python versions](#unit-testing-with-multiple-python-versions).

Expand All @@ -40,11 +40,13 @@ easily setup multiple Python versions. For
1. Install PyEnv -
`curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash`
1. Restart shell so the path changes take effect - `exec $SHELL`
1. `pyenv install 3.8.16`
1. `pyenv install 3.9.16`
1. `pyenv install 3.10.9`
1. `pyenv install 3.10.20`
1. `pyenv install 3.11.15`
1. `pyenv install 3.12.13`
1. `pyenv install 3.13.12`
1. `pyenv install 3.14.3`
3. Make Python versions available in the project:
`pyenv local 3.8.16 3.9.16 3.10.9`
`pyenv local 3.10.20 3.11.15 3.12.13 3.13.12 3.14.3`

Note: also make sure the following lines were written into your `.bashrc` (or `.zshrc`, depending on which shell you are using):
```
Expand All @@ -65,7 +67,7 @@ can be found [here](https://black.readthedocs.io/en/stable/integrations/editors.
Since black is installed in virtualenv, when you follow [this instruction](https://black.readthedocs.io/en/stable/integrations/editors.html), `which black` might give you this

```bash
(sam38) $ where black
(sam310) $ where black
/Users/<username>/.pyenv/shims/black
```

Expand All @@ -76,11 +78,11 @@ and this will happen:
pyenv: black: command not found

The `black' command exists in these Python versions:
3.8.16/envs/sam38
sam38
3.10.16/envs/sam310
sam310
```

A simple workaround is to use `/Users/<username>/.pyenv/versions/sam38/bin/black`
A simple workaround is to use `/Users/<username>/.pyenv/versions/sam310/bin/black`
instead of `/Users/<username>/.pyenv/shims/black`.

#### Pre-commit
Expand All @@ -98,15 +100,15 @@ handy plugin that can create virtualenv.
Depending on the python version, the following commands would change to
be the appropriate python version.

1. Create Virtualenv `sam38` for Python3.8: `pyenv virtualenv 3.8.16 sam38`
1. Activate Virtualenv: `pyenv activate sam38`
1. Create Virtualenv `sam310` for Python3.10: `pyenv virtualenv 3.10.16 sam310`
1. Activate Virtualenv: `pyenv activate sam310`

### 4. Install dev version of SAM transform

We will install a development version of SAM transform from source into the
virtualenv.

1. Activate Virtualenv: `pyenv activate sam38`
1. Activate Virtualenv: `pyenv activate sam310`
1. Install dev version of SAM transform: `make init`

Running tests
Expand All @@ -120,10 +122,10 @@ Run `make test` or `make test-fast`. Once all tests pass make sure to run

### Unit testing with multiple Python versions

Currently, our officially supported Python versions are 3.8, 3.9 and 3.10. For the most
part, code that works in Python3.8 will work in Pythons 3.9 and 3.10. You only run into problems if you are
trying to use features released in a higher version (for example features introduced into Python3.10
will not work in Python3.9). If you want to test in many versions, you can create a virtualenv for
Currently, our officially supported Python versions are 3.10, 3.11, 3.12, 3.13 and 3.14. For the most
part, code that works in Python 3.10 will work in later versions. You only run into problems if you are
trying to use features released in a higher version (for example features introduced into Python 3.13
will not work in Python 3.12). If you want to test in many versions, you can create a virtualenv for
each version and flip between them (sourcing the activate script). Typically, we run all tests in
one python version locally and then have our ci (appveyor) run all supported versions.

Expand Down
3 changes: 1 addition & 2 deletions bin/_file_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import sys
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Type


class FileFormatter(ABC):
Expand Down Expand Up @@ -34,7 +33,7 @@ def format_str(self, input_str: str) -> str:

@staticmethod
@abstractmethod
def decode_exception() -> Type[Exception]:
def decode_exception() -> type[Exception]:
"""Return the exception class when the file content cannot be decoded."""

@staticmethod
Expand Down
14 changes: 8 additions & 6 deletions bin/add_transform_test.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env python
"""Automatically create transform tests input and output files given an input template."""

import argparse
import json
import shutil
import subprocess
import sys
from copy import deepcopy
from pathlib import Path
from typing import Any, Dict
from typing import Any
from unittest.mock import patch

import boto3
Expand All @@ -31,12 +32,12 @@
CLI_OPTIONS = parser.parse_args()


def read_json_file(file_path: Path) -> Dict[str, Any]:
template: Dict[str, Any] = json.loads(file_path.read_text(encoding="utf-8"))
def read_json_file(file_path: Path) -> dict[str, Any]:
template: dict[str, Any] = json.loads(file_path.read_text(encoding="utf-8"))
return template


def write_json_file(obj: Dict[str, Any], file_path: Path) -> None:
def write_json_file(obj: dict[str, Any], file_path: Path) -> None:
with file_path.open("w", encoding="utf-8") as f:
json.dump(obj, f, indent=2, sort_keys=True)

Expand All @@ -54,8 +55,9 @@ def generate_transform_test_output_files(input_file_path: Path, file_basename: s
}

for _, (region, output_path) in transform_test_output_paths.items():
with patch("samtranslator.translator.arn_generator._get_region_from_session", return_value=region), patch(
"boto3.session.Session.region_name", region
with (
patch("samtranslator.translator.arn_generator._get_region_from_session", return_value=region),
patch("boto3.session.Session.region_name", region),
):
# Implicit API Plugin may alter input template file, thus passing a copy here.
output_fragment = transform(deepcopy(manifest), {}, ManagedPolicyLoader(iam_client))
Expand Down
4 changes: 2 additions & 2 deletions bin/json-format.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/usr/bin/env python
"""JSON file formatter (without prettier)."""

import sys
from pathlib import Path

# To allow this script to be executed from other directories
sys.path.insert(0, str(Path(__file__).absolute().parent.parent))

import json
from typing import Type

from bin._file_formatter import FileFormatter

Expand All @@ -23,7 +23,7 @@ def format_str(self, input_str: str) -> str:
return json.dumps(obj, indent=2, sort_keys=True) + "\n"

@staticmethod
def decode_exception() -> Type[Exception]:
def decode_exception() -> type[Exception]:
return json.JSONDecodeError

@staticmethod
Expand Down
4 changes: 2 additions & 2 deletions bin/parse_cdk_cfn_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@

import json
import sys
from typing import Any, Dict
from typing import Any


def main() -> None:
obj = json.load(sys.stdin)

out: Dict[str, Any] = {"properties": {}}
out: dict[str, Any] = {"properties": {}}
for k, v in obj["Types"].items():
kk = k.replace(".", " ")
vv = v["properties"]
Expand Down
45 changes: 23 additions & 22 deletions bin/public_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
(see https://peps.python.org/pep-0008/#descriptive-naming-styles)
This CLI tool helps automate the detection of compatibility-breaking changes.
"""

import argparse
import ast
import importlib
Expand All @@ -17,17 +18,17 @@
import string
import sys
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Set, Union
from typing import Any, NamedTuple, Union

_ARGUMENT_SELF = {"kind": "POSITIONAL_OR_KEYWORD", "name": "self"}
_PRINTABLE_CHARS = set(string.printable)


class InterfaceScanner:
def __init__(self, skipped_modules: Optional[List[str]] = None) -> None:
self.signatures: Dict[str, Union[inspect.Signature]] = {}
self.variables: Set[str] = set()
self.skipped_modules: Set[str] = set(skipped_modules or [])
def __init__(self, skipped_modules: list[str] | None = None) -> None:
self.signatures: dict[str, Union[inspect.Signature]] = {}
self.variables: set[str] = set()
self.skipped_modules: set[str] = set(skipped_modules or [])

def scan_interfaces_recursively(self, module_name: str) -> None:
if module_name in self.skipped_modules:
Expand Down Expand Up @@ -63,7 +64,7 @@ def _scan_variables_in_module(self, module_name: str) -> None:
else:
module_path = module_path.with_suffix(".py")
tree = ast.parse("".join([char for char in module_path.read_text() if char in _PRINTABLE_CHARS]))
assignments: List[ast.Assign] = [node for node in ast.iter_child_nodes(tree) if isinstance(node, ast.Assign)]
assignments: list[ast.Assign] = [node for node in ast.iter_child_nodes(tree) if isinstance(node, ast.Assign)]
for assignment in assignments:
for target in assignment.targets:
if not isinstance(target, ast.Name):
Expand Down Expand Up @@ -97,8 +98,8 @@ def _scan_methods_in_class(self, class_name: str, _class: Any) -> None:
self.signatures[full_path] = inspect.signature(method)


def _print(signature: Dict[str, inspect.Signature], variables: Set[str]) -> None:
result: Dict[str, Any] = {"routines": {}, "variables": sorted(variables)}
def _print(signature: dict[str, inspect.Signature], variables: set[str]) -> None:
result: dict[str, Any] = {"routines": {}, "variables": sorted(variables)}
for key, value in signature.items():
result["routines"][key] = [
(
Expand All @@ -116,23 +117,23 @@ def _print(signature: Dict[str, inspect.Signature], variables: Set[str]) -> None


class _BreakingChanges(NamedTuple):
deleted_variables: List[str]
deleted_routines: List[str]
incompatible_routines: List[str]
deleted_variables: list[str]
deleted_routines: list[str]
incompatible_routines: list[str]

def is_empty(self) -> bool:
return not any([self.deleted_variables, self.deleted_routines, self.incompatible_routines])

@staticmethod
def _argument_to_str(argument: Dict[str, Any]) -> str:
def _argument_to_str(argument: dict[str, Any]) -> str:
if "default" in argument:
return f'{argument["name"]}={argument["default"]}'
return str(argument["name"])

def print_markdown(
self,
original_routines: Dict[str, List[Dict[str, Any]]],
routines: Dict[str, List[Dict[str, Any]]],
original_routines: dict[str, list[dict[str, Any]]],
routines: dict[str, list[dict[str, Any]]],
) -> None:
"""Print all breaking changes in markdown."""
print("\n# Compatibility breaking changes:")
Expand All @@ -156,7 +157,7 @@ def print_markdown(


def _only_new_optional_arguments_or_existing_arguments_optionalized_or_var_arguments(
original_arguments: List[Dict[str, Any]], arguments: List[Dict[str, Any]]
original_arguments: list[dict[str, Any]], arguments: list[dict[str, Any]]
) -> bool:
if len(original_arguments) > len(arguments):
return False
Expand All @@ -178,7 +179,7 @@ def _only_new_optional_arguments_or_existing_arguments_optionalized_or_var_argum
)


def _is_compatible(original_arguments: List[Dict[str, Any]], arguments: List[Dict[str, Any]]) -> bool:
def _is_compatible(original_arguments: list[dict[str, Any]], arguments: list[dict[str, Any]]) -> bool:
"""
If there is an argument change, it is compatible only when
- new optional arguments are added or existing arguments become optional.
Expand All @@ -201,13 +202,13 @@ def _is_compatible(original_arguments: List[Dict[str, Any]], arguments: List[Dic


def _detect_breaking_changes(
original_routines: Dict[str, List[Dict[str, Any]]],
original_variables: Set[str],
routines: Dict[str, List[Dict[str, Any]]],
variables: Set[str],
original_routines: dict[str, list[dict[str, Any]]],
original_variables: set[str],
routines: dict[str, list[dict[str, Any]]],
variables: set[str],
) -> _BreakingChanges:
deleted_routines: List[str] = []
incompatible_routines: List[str] = []
deleted_routines: list[str] = []
incompatible_routines: list[str] = []
for routine_path, arguments in original_routines.items():
if routine_path not in routines:
deleted_routines.append(routine_path)
Expand Down
4 changes: 3 additions & 1 deletion bin/run_cfn_lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ fi

"${VENV}/bin/python" -m pip install cfn-lint --upgrade --quiet
# update cfn schema with retry logic (can fail due to network issues)
# --regions us-east-1 avoids a cfn-lint bug where updating all regions causes a
# multiprocessing pickle error. See https://github.com/aws-cloudformation/cfn-lint/issues/4379
MAX_RETRIES=3
RETRY_COUNT=0
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
if "${VENV}/bin/cfn-lint" -u; then
if "${VENV}/bin/cfn-lint" -u --regions us-east-1; then
echo "Successfully updated cfn-lint schema"
break
else
Expand Down
4 changes: 2 additions & 2 deletions bin/sam-translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

Known limitations: cannot transform CodeUri pointing at local directory.
"""

import argparse
import json
import logging
Expand All @@ -12,7 +13,6 @@
import sys
from functools import reduce
from pathlib import Path
from typing import List

import boto3

Expand Down Expand Up @@ -71,7 +71,7 @@
logging.basicConfig()


def execute_command(command: str, args: List[str]) -> None:
def execute_command(command: str, args: list[str]) -> None:
try:
aws_cmd = "aws" if platform.system().lower() != "windows" else "aws.cmd"
command_with_args = [aws_cmd, "cloudformation", command, *list(args)]
Expand Down
5 changes: 3 additions & 2 deletions bin/transform-test-error-json-format.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
It makes error json easier to review by breaking down "errorMessage"
into list of strings (delimiter: ". ").
"""

import sys
from pathlib import Path

# To allow this script to be executed from other directories
sys.path.insert(0, str(Path(__file__).absolute().parent.parent))

import json
from typing import Final, Type
from typing import Final

from bin._file_formatter import FileFormatter

Expand Down Expand Up @@ -41,7 +42,7 @@ def format_str(self, input_str: str) -> str:
return json.dumps(obj, indent=2, sort_keys=True) + "\n"

@staticmethod
def decode_exception() -> Type[Exception]:
def decode_exception() -> type[Exception]:
return json.JSONDecodeError

@staticmethod
Expand Down
Loading
Loading