diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7aa68214..2bc936ac7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 3.7, 3.8, 3.9, "3.10", "3.11", "3.12" ] + python-version: [ 3.8, 3.9, "3.10", "3.11", "3.12" ] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test_outdated_versions.yml b/.github/workflows/test_outdated_versions.yml index a1c9a2466..f6cf99322 100644 --- a/.github/workflows/test_outdated_versions.yml +++ b/.github/workflows/test_outdated_versions.yml @@ -46,7 +46,7 @@ jobs: - name: Start MotoServer run: | python -m build - docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh & + docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.9-slim /moto/scripts/ci_moto_server.sh & python scripts/ci_wait_for_server.py - name: Test ServerMode/Coverage env: diff --git a/.github/workflows/test_terraform.yml b/.github/workflows/test_terraform.yml index 240ac55c3..194c6d598 100644 --- a/.github/workflows/test_terraform.yml +++ b/.github/workflows/test_terraform.yml @@ -35,7 +35,7 @@ jobs: run: | pip install PyYAML build python -m build - docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e MOTO_PORT=4566 -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh & + docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e MOTO_PORT=4566 -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock python:3.9-slim /moto/scripts/ci_moto_server.sh & MOTO_PORT=4566 python scripts/ci_wait_for_server.py - name: Get list of tests for this service id: get-list diff --git a/.github/workflows/tests_decoratormode.yml b/.github/workflows/tests_decoratormode.yml index f7cc869b5..fdf2576b0 100644 --- a/.github/workflows/tests_decoratormode.yml +++ b/.github/workflows/tests_decoratormode.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/tests_sdk_java.yml b/.github/workflows/tests_sdk_java.yml index aac136488..12fe3cfca 100644 --- a/.github/workflows/tests_sdk_java.yml +++ b/.github/workflows/tests_sdk_java.yml @@ -12,7 +12,7 @@ jobs: - name: Set up Python 3.8 uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: "3.10" - name: Start MotoServer run: | pip install build diff --git a/.github/workflows/tests_servermode.yml b/.github/workflows/tests_servermode.yml index 06c6fe444..534b96268 100644 --- a/.github/workflows/tests_servermode.yml +++ b/.github/workflows/tests_servermode.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/codecov.yml b/codecov.yml index 69c33d4f8..750eb3b17 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,7 +1,7 @@ codecov: notify: # Leave a GitHub comment after all builds have passed - after_n_builds: 12 + after_n_builds: 10 coverage: status: project: diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py index 07dd02d40..331bf0454 100644 --- a/moto/apigateway/models.py +++ b/moto/apigateway/models.py @@ -1578,10 +1578,6 @@ class APIGatewayBackend(BaseBackend): validate(api_doc) # type: ignore[arg-type] except OpenAPIValidationError as e: raise InvalidOpenAPIDocumentException(e) - except AttributeError: - # Call can fail in Python3.7 due to `typing_extensions 4.6.0` throwing an error - # Easiest to just ignore this for now - Py3.7 is EOL soon anyway - pass name = api_doc["info"]["title"] description = api_doc["info"]["description"] api = self.create_rest_api(name=name, description=description) @@ -1653,10 +1649,6 @@ class APIGatewayBackend(BaseBackend): validate(api_doc) # type: ignore[arg-type] except OpenAPIValidationError as e: raise InvalidOpenAPIDocumentException(e) - except AttributeError: - # Call can fail in Python3.7 due to `typing_extensions 4.6.0` throwing an error - # Easiest to just ignore this for now - Py3.7 is EOL soon anyway - pass if mode == "overwrite": api = self.get_rest_api(function_id) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index 3f4310744..bc177aec6 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -17,7 +17,7 @@ from collections import defaultdict from datetime import datetime from gzip import GzipFile from sys import platform -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Dict, Iterable, List, Optional, Tuple, TypedDict, Union import requests.exceptions @@ -64,6 +64,11 @@ from .utils import ( logger = logging.getLogger(__name__) +class LayerDataType(TypedDict): + Arn: str + CodeSize: int + + def zip2tar(zip_bytes: bytes) -> io.BytesIO: tarstream = io.BytesIO() timeshift = int((datetime.now() - utcnow()).total_seconds()) @@ -176,7 +181,7 @@ class _DockerDataVolumeLayerContext: def __init__(self, lambda_func: "LambdaFunction"): self._lambda_func = lambda_func - self._layers: List[Dict[str, str]] = self._lambda_func.layers + self._layers: List[LayerDataType] = self._lambda_func.layers self._vol_ref: Optional[_VolumeRefCount] = None @property @@ -623,9 +628,7 @@ class LambdaFunction(CloudFormationModel, DockerModel): self.package_type = spec.get("PackageType", "Zip") self.publish = spec.get("Publish", False) # this is ignored currently self.timeout = spec.get("Timeout", 3) - self.layers: List[Dict[str, str]] = self._get_layers_data( - spec.get("Layers", []) - ) + self.layers: List[LayerDataType] = self._get_layers_data(spec.get("Layers", [])) self.signing_profile_version_arn = spec.get("SigningProfileVersionArn") self.signing_job_arn = spec.get("SigningJobArn") self.code_signing_config_arn = spec.get("CodeSigningConfigArn") @@ -720,7 +723,7 @@ class LambdaFunction(CloudFormationModel, DockerModel): def __repr__(self) -> str: return json.dumps(self.get_configuration()) - def _get_layers_data(self, layers_versions_arns: List[str]) -> List[Dict[str, str]]: + def _get_layers_data(self, layers_versions_arns: List[str]) -> List[LayerDataType]: backend = lambda_backends[self.account_id][self.region] layer_versions = [ backend.layers_versions_by_arn(layer_version) @@ -730,9 +733,9 @@ class LambdaFunction(CloudFormationModel, DockerModel): raise UnknownLayerVersionException(layers_versions_arns) # The `if lv` part is not necessary - we know there are no None's, because of the `all()`-check earlier # But MyPy does not seem to understand this - # The `type: ignore` is because `code_size` is an int, and we're returning Dict[str, str] - # We should convert the return-type into a TypedDict the moment we drop Py3.7 support - return [{"Arn": lv.arn, "CodeSize": lv.code_size} for lv in layer_versions if lv] # type: ignore + return [ + {"Arn": lv.arn, "CodeSize": lv.code_size} for lv in layer_versions if lv + ] def get_code_signing_config(self) -> Dict[str, Any]: return { diff --git a/moto/core/models.py b/moto/core/models.py index 9d5dc2c0e..648ac1903 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -122,12 +122,7 @@ class BaseMockAWS(ContextManager["BaseMockAWS"]): if self.__class__.nested_count == 0: if self.__class__.mocks_active: - try: - self.default_session_mock.stop() - except RuntimeError: - # We only need to check for this exception in Python 3.7 - # https://bugs.python.org/issue36366 - pass + self.default_session_mock.stop() self.unmock_env_variables() self.__class__.mocks_active = False if remove_data: diff --git a/moto/core/versions.py b/moto/core/versions.py index 3c42a2b8c..07769fed6 100644 --- a/moto/core/versions.py +++ b/moto/core/versions.py @@ -1,13 +1,8 @@ import sys +from importlib.metadata import version from moto.utilities.distutils_version import LooseVersion -try: - from importlib.metadata import version -except ImportError: - from importlib_metadata import version # type: ignore[no-redef] - - PYTHON_VERSION_INFO = sys.version_info PYTHON_311 = sys.version_info >= (3, 11) RESPONSES_VERSION = version("responses") diff --git a/moto/resourcegroupstaggingapi/models.py b/moto/resourcegroupstaggingapi/models.py index 90c7a76ab..20bac0c4e 100644 --- a/moto/resourcegroupstaggingapi/models.py +++ b/moto/resourcegroupstaggingapi/models.py @@ -133,8 +133,6 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend): and v in vl ) - # Any in this case means: str | Optional[str] - # When we drop Python 3.7 we should look into replacing this with a TypedDict def tag_filter(tag_list: List[Dict[str, Any]]) -> bool: result = [] if tag_filters: diff --git a/requirements-dev.txt b/requirements-dev.txt index 509bc1ce8..96261eac6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,8 +6,7 @@ click inflection lxml mypy -typing-extensions<=4.5.0; python_version < '3.8' -typing-extensions; python_version >= '3.8' +typing-extensions packaging build prompt_toolkit diff --git a/setup.cfg b/setup.cfg index db489cfa8..b3bc9264c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,6 @@ license = Apache License 2.0 test_suite = tests classifiers = Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -25,7 +24,7 @@ project_urls = Changelog = https://github.com/getmoto/moto/blob/master/CHANGELOG.md [options] -python_requires = >=3.7 +python_requires = >=3.8 install_requires = boto3>=1.9.201 botocore>=1.12.201 @@ -36,7 +35,6 @@ install_requires = python-dateutil<3.0.0,>=2.1 responses>=0.13.0 Jinja2>=2.10.1 - importlib_metadata ; python_version < '3.8' package_dir = moto = moto include_package_data = True diff --git a/tests/test_apigateway/test_apigateway_importrestapi.py b/tests/test_apigateway/test_apigateway_importrestapi.py index bfdf0b785..07d0c73e3 100644 --- a/tests/test_apigateway/test_apigateway_importrestapi.py +++ b/tests/test_apigateway/test_apigateway_importrestapi.py @@ -1,12 +1,10 @@ import os -import sys -from unittest import SkipTest import boto3 import pytest from botocore.exceptions import ClientError -from moto import mock_apigateway, settings +from moto import mock_apigateway @mock_apigateway @@ -47,9 +45,6 @@ def test_import_rest_api__nested_api(): @mock_apigateway def test_import_rest_api__invalid_api_creates_nothing(): - if sys.version_info < (3, 8) or settings.TEST_SERVER_MODE: - raise SkipTest("openapi-module throws an error in Py3.7") - client = boto3.client("apigateway", region_name="us-west-2") path = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/test_apigateway/test_apigateway_putrestapi.py b/tests/test_apigateway/test_apigateway_putrestapi.py index ea83618fa..fc42bef65 100644 --- a/tests/test_apigateway/test_apigateway_putrestapi.py +++ b/tests/test_apigateway/test_apigateway_putrestapi.py @@ -1,12 +1,10 @@ import os -import sys -from unittest import SkipTest import boto3 import pytest from botocore.exceptions import ClientError -from moto import mock_apigateway, settings +from moto import mock_apigateway @mock_apigateway @@ -145,9 +143,6 @@ def test_put_rest_api__existing_methods_still_exist(): @mock_apigateway def test_put_rest_api__fail_on_invalid_spec(): - if sys.version_info < (3, 8) or settings.TEST_SERVER_MODE: - raise SkipTest("openapi-module throws an error in Py3.7") - client = boto3.client("apigateway", region_name="us-east-2") response = client.create_rest_api(name="my_api", description="this is my api") diff --git a/tests/test_awslambda/test_awslambda_cloudformation.py b/tests/test_awslambda/test_awslambda_cloudformation.py index 58c70e8fb..ddff000f3 100644 --- a/tests/test_awslambda/test_awslambda_cloudformation.py +++ b/tests/test_awslambda/test_awslambda_cloudformation.py @@ -85,7 +85,7 @@ def test_lambda_can_be_updated_by_cloudformation(): # Verify function has been created created_fn = lmbda.get_function(FunctionName=created_fn_name) assert created_fn["Configuration"]["Handler"] == "lambda_function.lambda_handler1" - assert created_fn["Configuration"]["Runtime"] == "python3.7" + assert created_fn["Configuration"]["Runtime"] == "python3.9" assert "/test1.zip" in created_fn["Code"]["Location"] # Update CF stack cf.update_stack(StackName=stack_name, TemplateBody=body2) @@ -97,7 +97,7 @@ def test_lambda_can_be_updated_by_cloudformation(): == created_fn["Configuration"]["FunctionArn"] ) assert updated_fn["Configuration"]["Handler"] == "lambda_function.lambda_handler2" - assert updated_fn["Configuration"]["Runtime"] == "python3.8" + assert updated_fn["Configuration"]["Runtime"] == "python3.10" assert "/test2.zip" in updated_fn["Code"]["Location"] @@ -310,8 +310,8 @@ def create_stack(cf, s3): s3.create_bucket(Bucket=bucket_name) s3.put_object(Bucket=bucket_name, Key="test1.zip", Body=get_zip_file()) s3.put_object(Bucket=bucket_name, Key="test2.zip", Body=get_zip_file()) - body1 = get_template(bucket_name, "1", "python3.7") - body2 = get_template(bucket_name, "2", "python3.8") + body1 = get_template(bucket_name, "1", "python3.9") + body2 = get_template(bucket_name, "2", "python3.10") stack = cf.create_stack(StackName=stack_name, TemplateBody=body1) return body2, stack