From 025357d474ba37a8212cf4741d894eff4d3b8cd8 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 4 Jul 2023 17:24:40 -0700 Subject: [PATCH 1/6] chore: use amazon ecr credential helper in windows appveyor (#5446) --- appveyor-windows.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/appveyor-windows.yml b/appveyor-windows.yml index 886e65e0c4..28567c956f 100644 --- a/appveyor-windows.yml +++ b/appveyor-windows.yml @@ -134,7 +134,14 @@ install: # Echo final Path - "echo %PATH%" - - "IF DEFINED BY_CANARY ECHO Logging in Public ECR && aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws" + # use amazon-ecr-credential-helper + - choco install amazon-ecr-credential-helper + - ps: " + $docker_config = Get-Content $env:HOME/.docker/config.json -raw | ConvertFrom-Json; + $docker_config.credsStore = 'ecr-login'; + $docker_config | ConvertTo-Json | set-content $env:HOME/.docker/config.json; + " + - ps: "get-content $env:HOME/.docker/config.json" # claim some disk space before starting the tests - "docker system prune -a -f" From b6b4e398d5410f419cfe31c8db7eeaad4955cf00 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:08:35 -0700 Subject: [PATCH 2/6] chore: bump version to 1.90.0 (#5448) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index 48bdee91f7..1fea4bd55f 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.89.0" +__version__ = "1.90.0" From 3603e1247fbe655689b1239536914fd47a2ddc74 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:46:42 -0700 Subject: [PATCH 3/6] fix: Handler path mapping for layer-wrapped esbuild functions (#5450) * fix: Layer wrapping esbuild function handlers * Remove unused import * Use nodejs18 in tests --- samcli/lib/build/bundler.py | 4 +++ tests/end_to_end/test_runtimes_e2e.py | 27 +++++++++++++++++++ .../esbuild-datadog-integration/main.js | 8 ++++++ .../esbuild-datadog-integration/template.yaml | 27 +++++++++++++++++++ tests/unit/lib/build_module/test_bundler.py | 7 +++++ 5 files changed, 73 insertions(+) create mode 100644 tests/end_to_end/testdata/esbuild-datadog-integration/main.js create mode 100644 tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml diff --git a/samcli/lib/build/bundler.py b/samcli/lib/build/bundler.py index bc3774d3b9..cd69604083 100644 --- a/samcli/lib/build/bundler.py +++ b/samcli/lib/build/bundler.py @@ -12,6 +12,7 @@ LOG = logging.getLogger(__name__) +LAYER_PREFIX = "/opt" ESBUILD_PROPERTY = "esbuild" @@ -157,6 +158,9 @@ def _should_update_handler(self, handler: str, name: str) -> bool: if not handler_filename: LOG.debug("Unable to parse handler, continuing without post-processing template.") return False + if handler_filename.startswith(LAYER_PREFIX): + LOG.debug("Skipping updating the handler path as it is pointing to a layer.") + return False expected_artifact_path = Path(self._build_dir, name, handler_filename) return not expected_artifact_path.is_file() diff --git a/tests/end_to_end/test_runtimes_e2e.py b/tests/end_to_end/test_runtimes_e2e.py index ffe304b705..9955c28341 100644 --- a/tests/end_to_end/test_runtimes_e2e.py +++ b/tests/end_to_end/test_runtimes_e2e.py @@ -1,8 +1,10 @@ +from distutils.dir_util import copy_tree from unittest import skipIf import json from pathlib import Path +import os from parameterized import parameterized_class from tests.end_to_end.end_to_end_base import EndToEndBase @@ -163,3 +165,28 @@ def test_go_hello_world_default_workflow(self): DefaultDeleteStage(BaseValidator(e2e_context), e2e_context, delete_command_list, stack_name), ] self._run_tests(stages) + + +class TestEsbuildDatadogLayerIntegration(EndToEndBase): + app_template = "" + + def test_integration(self): + function_name = "HelloWorldFunction" + event = '{"hello": "world"}' + stack_name = self._method_to_stack_name(self.id()) + with EndToEndTestContext(self.app_name) as e2e_context: + project_path = str(Path("testdata") / "esbuild-datadog-integration") + os.mkdir(e2e_context.project_directory) + copy_tree(project_path, e2e_context.project_directory) + self.template_path = e2e_context.template_path + build_command_list = self.get_command_list() + deploy_command_list = self._get_deploy_command(stack_name) + remote_invoke_command_list = self._get_remote_invoke_command(stack_name, function_name, event, "json") + delete_command_list = self._get_delete_command(stack_name) + stages = [ + EndToEndBaseStage(BuildValidator(e2e_context), e2e_context, build_command_list), + EndToEndBaseStage(BaseValidator(e2e_context), e2e_context, deploy_command_list), + EndToEndBaseStage(RemoteInvokeValidator(e2e_context), e2e_context, remote_invoke_command_list), + DefaultDeleteStage(BaseValidator(e2e_context), e2e_context, delete_command_list, stack_name), + ] + self._run_tests(stages) diff --git a/tests/end_to_end/testdata/esbuild-datadog-integration/main.js b/tests/end_to_end/testdata/esbuild-datadog-integration/main.js new file mode 100644 index 0000000000..5ba65da324 --- /dev/null +++ b/tests/end_to_end/testdata/esbuild-datadog-integration/main.js @@ -0,0 +1,8 @@ +exports.lambdaHandler = async (event, context) => { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world!', + }), + }; +}; diff --git a/tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml b/tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml new file mode 100644 index 0000000000..3341557f3e --- /dev/null +++ b/tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml @@ -0,0 +1,27 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +# Latest extension version: https://github.com/DataDog/datadog-lambda-extension/releases +# Latest Node.js layer version: https://github.com/DataDog/datadog-lambda-js/releases + +Parameters: + DataDogLayers: + Description: DataDog layers + Type: CommaDelimitedList + Default: "arn:aws:lambda:us-east-1:464622532012:layer:Datadog-Node18-x:93, arn:aws:lambda:us-east-1:464622532012:layer:Datadog-Extension:44" + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: /opt/nodejs/node_modules/datadog-lambda-js/handler.handler + Runtime: nodejs18.x + Environment: + Variables: + DD_LAMBDA_HANDLER: main.lambdaHandler + Layers: !Ref DataDogLayers + Metadata: + BuildMethod: esbuild + BuildProperties: + EntryPoints: + - main.js \ No newline at end of file diff --git a/tests/unit/lib/build_module/test_bundler.py b/tests/unit/lib/build_module/test_bundler.py index 39fb1f7c32..c25543b09e 100644 --- a/tests/unit/lib/build_module/test_bundler.py +++ b/tests/unit/lib/build_module/test_bundler.py @@ -195,6 +195,13 @@ def test_check_invalid_lambda_handler_none_build_dir(self): return_val = bundler_manager._should_update_handler("", "") self.assertFalse(return_val) + def test_should_not_update_layer_path(self): + bundler_manager = EsbuildBundlerManager(Mock(), build_dir="/build/dir") + bundler_manager._get_path_and_filename_from_handler = Mock() + bundler_manager._get_path_and_filename_from_handler.return_value = "/opt/nodejs/node_modules/d/handler.handler" + return_val = bundler_manager._should_update_handler("", "") + self.assertFalse(return_val) + def test_update_function_handler(self): resources = { "FunctionA": { From cee2d3d05aa63024729d0f58e31454cc721f5c73 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:49:39 -0700 Subject: [PATCH 4/6] fix: fix macos reproducable task and gh actions (#5455) --- .../automated-updates-to-sam-cli.yml | 4 ++-- Makefile | 2 +- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 19 +++++-------------- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/automated-updates-to-sam-cli.yml b/.github/workflows/automated-updates-to-sam-cli.yml index 45c49f727a..a42123ea5b 100644 --- a/.github/workflows/automated-updates-to-sam-cli.yml +++ b/.github/workflows/automated-updates-to-sam-cli.yml @@ -75,7 +75,7 @@ jobs: - uses: actions/setup-python@v4 # used for make update-reproducible-reqs below with: python-version: | - 3.7 + 3.8 3.11 - name: Update aws-sam-translator & commit @@ -132,7 +132,7 @@ jobs: - uses: actions/setup-python@v4 # used for make update-reproducible-reqs below with: python-version: | - 3.7 + 3.8 3.11 - name: Upgrade aws_lambda_builders & commit diff --git a/Makefile b/Makefile index 8876d482b2..dd80957cae 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ update-reproducible-linux-reqs: venv-update-reproducible-linux/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-linux.txt update-reproducible-mac-reqs: - python3.7 -m venv venv-update-reproducible-mac + python3.8 -m venv venv-update-reproducible-mac venv-update-reproducible-mac/bin/pip install --upgrade pip-tools pip venv-update-reproducible-mac/bin/pip install -r requirements/base.txt venv-update-reproducible-mac/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-mac.txt diff --git a/requirements/base.txt b/requirements/base.txt index f0626e7145..707300f4b8 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,7 +7,7 @@ jmespath~=1.0.1 ruamel_yaml~=0.17.32 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 -aws-sam-translator==1.70.0 +aws-sam-translator==1.71.0 #docker minor version updates can include breaking changes. Auto update micro version only. docker~=6.1.0 dateparser~=1.1 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index f946536226..7ed8c51903 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.70.0 \ - --hash=sha256:753288eda07b057e5350773b7617076962b59404d49cd05e2259ac96a7694436 \ - --hash=sha256:a2df321607d29791893707ef2ded9e79be00dbb71ac430696f6e6d7d0b0301a5 +aws-sam-translator==1.71.0 \ + --hash=sha256:17fb87c8137d8d49e7a978396b2b3b279211819dee44618415aab1e99c2cb659 \ + --hash=sha256:a3ea80aeb116d7978b26ac916d2a5a24d012b742bf28262b17769c4b886e8fba # via # aws-sam-cli (setup.py) # cfn-lint diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index a1db7a41cf..4ac1c38a21 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements/reproducible-mac.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.70.0 \ - --hash=sha256:753288eda07b057e5350773b7617076962b59404d49cd05e2259ac96a7694436 \ - --hash=sha256:a2df321607d29791893707ef2ded9e79be00dbb71ac430696f6e6d7d0b0301a5 +aws-sam-translator==1.71.0 \ + --hash=sha256:17fb87c8137d8d49e7a978396b2b3b279211819dee44618415aab1e99c2cb659 \ + --hash=sha256:a3ea80aeb116d7978b26ac916d2a5a24d012b742bf28262b17769c4b886e8fba # via # aws-sam-cli (setup.py) # cfn-lint @@ -270,12 +270,7 @@ idna==3.4 \ importlib-metadata==6.7.0 \ --hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \ --hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5 - # via - # attrs - # click - # flask - # jsonpickle - # jsonschema + # via flask importlib-resources==5.12.0 \ --hash=sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6 \ --hash=sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a @@ -722,12 +717,8 @@ typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via - # arrow # aws-sam-cli (setup.py) # aws-sam-translator - # importlib-metadata - # jsonschema - # markdown-it-py # pydantic # rich tzlocal==3.0 \ From 16a1740b635d366de19cbb54dd549cd27abdbc63 Mon Sep 17 00:00:00 2001 From: Elvis Henrique Pereira Date: Thu, 6 Jul 2023 18:12:15 -0300 Subject: [PATCH 5/6] feat(sync): support build-image option (#5441) * feat(sync): support build-image option * chore: adding build image option on help option --- samcli/commands/_utils/options.py | 23 +++++++++++++++++++ samcli/commands/build/command.py | 17 ++------------ samcli/commands/sync/command.py | 12 +++++++++- samcli/commands/sync/core/options.py | 1 + .../unit/commands/samconfig/test_samconfig.py | 1 + tests/unit/commands/sync/test_command.py | 6 +++++ 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index 5b1b55cc32..24f5eef3a6 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -796,6 +796,29 @@ def use_container_build_option(f): return use_container_build_click_option()(f) +def build_image_click_option(cls): + return click.option( + "--build-image", + "-bi", + default=None, + multiple=True, # Can pass in multiple build images + required=False, + help="Container image URIs for building functions/layers. " + "You can specify for all functions/layers with just the image URI " + "(--build-image public.ecr.aws/sam/build-nodejs18.x:latest). " + "You can specify for each individual function with " + "(--build-image FunctionLogicalID=public.ecr.aws/sam/build-nodejs18.x:latest). " + "A combination of the two can be used. If a function does not have build image specified or " + "an image URI for all functions, the default SAM CLI build images will be used.", + cls=cls, + ) + + +@parameterized_option +def build_image_option(f, cls): + return build_image_click_option(cls)(f) + + def _space_separated_list_func_type(value): if isinstance(value, str): return value.split(" ") diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index 92a2c6578c..a60f9954e1 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -20,6 +20,7 @@ manifest_option, cached_option, use_container_build_option, + build_image_option, hook_name_click_option, ) from samcli.commands._utils.option_value_processor import process_env_var, process_image_options @@ -94,21 +95,7 @@ help="Environment variables json file (e.g., env_vars.json) to be passed to build containers.", cls=ContainerOptions, ) -@click.option( - "--build-image", - "-bi", - default=None, - multiple=True, # Can pass in multiple build images - required=False, - help="Container image URIs for building functions/layers. " - "You can specify for all functions/layers with just the image URI " - "(--build-image public.ecr.aws/sam/build-nodejs18.x:latest). " - "You can specify for each individual function with " - "(--build-image FunctionLogicalID=public.ecr.aws/sam/build-nodejs18.x:latest). " - "A combination of the two can be used. If a function does not have build image specified or " - "an image URI for all functions, the default SAM CLI build images will be used.", - cls=ContainerOptions, -) +@build_image_option(cls=ContainerOptions) @click.option( "--exclude", "-x", diff --git a/samcli/commands/sync/command.py b/samcli/commands/sync/command.py index 26eccac4b3..81f1222207 100644 --- a/samcli/commands/sync/command.py +++ b/samcli/commands/sync/command.py @@ -1,7 +1,7 @@ """CLI command for "sync" command.""" import logging import os -from typing import TYPE_CHECKING, List, Optional, Set +from typing import TYPE_CHECKING, List, Optional, Set, Tuple import click @@ -18,8 +18,10 @@ DEFAULT_CACHE_DIR, ) from samcli.commands._utils.custom_options.replace_help_option import ReplaceHelpSummaryOption +from samcli.commands._utils.option_value_processor import process_image_options from samcli.commands._utils.options import ( base_dir_option, + build_image_option, capabilities_option, image_repositories_option, image_repository_option, @@ -35,6 +37,7 @@ template_option_without_build, use_container_build_option, ) +from samcli.commands.build.click_container import ContainerOptions from samcli.commands.build.command import _get_mode_value_from_envvar from samcli.commands.sync.core.command import SyncCommand from samcli.commands.sync.sync_context import SyncContext @@ -155,6 +158,7 @@ @stack_name_option(required=True) # pylint: disable=E1120 @base_dir_option @use_container_build_option +@build_image_option(cls=ContainerOptions) @image_repository_option @image_repositories_option @s3_bucket_option(disable_callback=True) # pylint: disable=E1120 @@ -202,6 +206,7 @@ def cli( use_container: bool, config_file: str, config_env: str, + build_image: Optional[Tuple[str]], ) -> None: """ `sam sync` command entry point @@ -234,6 +239,7 @@ def cli( tags, metadata, use_container, + build_image, config_file, config_env, None, # TODO: replace with build_in_source once it's added as a click option @@ -265,6 +271,7 @@ def do_cli( tags: dict, metadata: dict, use_container: bool, + build_image: Optional[Tuple[str]], config_file: str, config_env: str, build_in_source: Optional[bool], @@ -303,6 +310,8 @@ def do_cli( LOG.debug("Using build directory as %s", build_dir) EventTracker.track_event("UsedFeature", "Accelerate") + processed_build_images = process_image_options(build_image) + with BuildContext( resource_identifier=None, template_file=template_file, @@ -320,6 +329,7 @@ def do_cli( print_success_message=False, locate_layer_nested=True, build_in_source=build_in_source, + build_images=processed_build_images, ) as build_context: built_template = os.path.join(build_dir, DEFAULT_TEMPLATE_NAME) diff --git a/samcli/commands/sync/core/options.py b/samcli/commands/sync/core/options.py index 43a63e92e7..ee7ead9799 100644 --- a/samcli/commands/sync/core/options.py +++ b/samcli/commands/sync/core/options.py @@ -25,6 +25,7 @@ "notification_arns", "tags", "metadata", + "build_image", ] CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index 19b4a7672d..675f22a4bf 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -1007,6 +1007,7 @@ def test_sync( {"a": "tag1", "b": "tag with spaces"}, {"m1": "value1", "m2": "value2"}, True, + (), "samconfig.toml", "default", None, diff --git a/tests/unit/commands/sync/test_command.py b/tests/unit/commands/sync/test_command.py index b0bcede4d0..6599f04076 100644 --- a/tests/unit/commands/sync/test_command.py +++ b/tests/unit/commands/sync/test_command.py @@ -57,6 +57,7 @@ def setUp(self): self.clean = True self.config_env = "mock-default-env" self.config_file = "mock-default-filename" + self.build_image = None MOCK_SAM_CONFIG.reset_mock() @parameterized.expand( @@ -141,6 +142,7 @@ def test_infra_must_succeed_sync( self.tags, self.metadata, use_container, + self.build_image, self.config_file, self.config_env, build_in_source=False, @@ -167,6 +169,7 @@ def test_infra_must_succeed_sync( print_success_message=False, locate_layer_nested=True, build_in_source=False, + build_images={}, ) PackageContextMock.assert_called_with( @@ -298,6 +301,7 @@ def test_watch_must_succeed_sync( self.tags, self.metadata, use_container, + self.build_image, self.config_file, self.config_env, build_in_source=False, @@ -320,6 +324,7 @@ def test_watch_must_succeed_sync( print_success_message=False, locate_layer_nested=True, build_in_source=False, + build_images={}, ) PackageContextMock.assert_called_with( @@ -443,6 +448,7 @@ def test_code_must_succeed_sync( self.tags, self.metadata, use_container, + self.build_image, self.config_file, self.config_env, build_in_source=None, From 58faff0dfc597016f945ca215925ca26b950c770 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:33:07 -0700 Subject: [PATCH 6/6] fix: Avoid Certain Depedendency Version (#5460) * Avoid broken click version * Pin boto3 and jsonschema * Update reproducible reqs * Ignore deprecation warnings in pytest * Pin jsonschema --- pytest.ini | 4 ++++ requirements/base.txt | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 9d19677545..80263254ba 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,3 +6,7 @@ filterwarnings = error ignore::DeprecationWarning:docker default::ResourceWarning +; The following deprecation warnings are treated as failures unless we explicitly tell pytest not to +; Remove once we no longer support python3.7 + ignore::boto3.exceptions.PythonDeprecationWarning + diff --git a/requirements/base.txt b/requirements/base.txt index 707300f4b8..38532b4714 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,5 +1,6 @@ chevron~=0.12 -click~=8.0 +# 8.1.4 of Click has an issue with the typing breaking the linter - https://github.com/pallets/click/issues/2558 +click~=8.0,!=8.1.4 Flask<2.3 #Need to add latest lambda changes which will return invoke mode details boto3>=1.26.109,==1.* @@ -18,6 +19,8 @@ tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 pyopenssl~=23.2.0 +# Pin to <4.18 to until SAM-T no longer uses RefResolver +jsonschema<4.18 # Needed for supporting Protocol in Python 3.7, Protocol class became public with python3.8 typing_extensions~=4.4.0