diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 015e310..2c7280b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: set up python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.8' - name: install deps run: pip install -r requirements-test.txt @@ -33,7 +33,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: ['3.9', '3.10', '3.11', '3.12'] + python: ['3.8', '3.9', '3.10', '3.11', '3.12'] env: PYTHON: ${{ matrix.python }} @@ -81,7 +81,7 @@ jobs: - name: set up python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.8' - name: install run: pip install -U build diff --git a/pydantic_settings_aws/aws.py b/pydantic_settings_aws/aws.py index 393f6d8..64e6814 100644 --- a/pydantic_settings_aws/aws.py +++ b/pydantic_settings_aws/aws.py @@ -1,5 +1,5 @@ import json -from typing import Any, AnyStr, Dict, Optional, Union +from typing import Any, AnyStr, Dict, Optional, Type, Union import boto3 # type: ignore[import-untyped] from pydantic import ValidationError @@ -10,7 +10,7 @@ def get_ssm_content( - settings: type[BaseSettings], + settings: Type[BaseSettings], field_name: str, ssm_info: Optional[Union[Dict[Any, AnyStr], AnyStr]] = None ) -> Optional[str]: @@ -36,14 +36,14 @@ def get_ssm_content( client = _get_ssm_boto3_client(settings) logger.debug(f"Getting parameter {ssm_name} value with boto3 client") - ssm_response: dict[str, Any] = client.get_parameter( # type: ignore + ssm_response: Dict[str, Any] = client.get_parameter( # type: ignore Name=ssm_name, WithDecryption=True ) return ssm_response.get("Parameter", {}).get("Value", None) -def get_secrets_content(settings: type[BaseSettings]) -> dict[str, Any]: +def get_secrets_content(settings: Type[BaseSettings]) -> Dict[str, Any]: client = _get_secrets_boto3_client(settings) secrets_args: AwsSecretsArgs = _get_secrets_args(settings) @@ -69,7 +69,7 @@ def get_secrets_content(settings: type[BaseSettings]) -> dict[str, Any]: raise json_err -def _get_secrets_boto3_client( settings: type[BaseSettings]): # type: ignore[no-untyped-def] +def _get_secrets_boto3_client( settings: Type[BaseSettings]): # type: ignore[no-untyped-def] logger.debug("Getting secrets manager content.") client = settings.model_config.get("secrets_client", None) @@ -80,7 +80,7 @@ def _get_secrets_boto3_client( settings: type[BaseSettings]): # type: ignore[no- return _create_secrets_client(settings) -def _create_secrets_client(settings: type[BaseSettings]): # type: ignore[no-untyped-def] +def _create_secrets_client(settings: Type[BaseSettings]): # type: ignore[no-untyped-def] """Create a boto3 client for secrets manager. Neither `boto3` nor `pydantic` exceptions will be handled. @@ -92,7 +92,7 @@ def _create_secrets_client(settings: type[BaseSettings]): # type: ignore[no-unt SecretsManagerClient: A secrets manager boto3 client. """ logger.debug("Extracting settings prefixed with aws_") - args: dict[str, Any] = { + args: Dict[str, Any] = { k: v for k, v in settings.model_config.items() if k.startswith("aws_") } @@ -105,11 +105,11 @@ def _create_secrets_client(settings: type[BaseSettings]): # type: ignore[no-unt return session.client("secretsmanager") -def _get_secrets_args(settings: type[BaseSettings]) -> AwsSecretsArgs: +def _get_secrets_args(settings: Type[BaseSettings]) -> AwsSecretsArgs: logger.debug( "Extracting settings prefixed with secrets_, except _client and _dir" ) - args: dict[str, Any] = { + args: Dict[str, Any] = { k: v for k, v in settings.model_config.items() if k.startswith("secrets_") @@ -139,7 +139,7 @@ def _get_secrets_content( logger.debug( "SecretString was not present. Getting content from SecretBinary." ) - secret_binary: bytes | None = secret.get("SecretBinary") + secret_binary: Optional[bytes] = secret.get("SecretBinary") if secret_binary: try: @@ -152,7 +152,7 @@ def _get_secrets_content( return secrets_content -def _get_ssm_boto3_client(settings: type[BaseSettings]): # type: ignore[no-untyped-def] +def _get_ssm_boto3_client(settings: Type[BaseSettings]): # type: ignore[no-untyped-def] logger.debug("Getting secrets manager content.") client = settings.model_config.get("ssm_client", None) @@ -165,7 +165,7 @@ def _get_ssm_boto3_client(settings: type[BaseSettings]): # type: ignore[no-untyp return _create_ssm_client(settings) -def _create_ssm_client(settings: type[BaseSettings]): # type: ignore[no-untyped-def] +def _create_ssm_client(settings: Type[BaseSettings]): # type: ignore[no-untyped-def] """Create a boto3 client for parameter store. Neither `boto3` nor `pydantic` exceptions will be handled. @@ -177,7 +177,7 @@ def _create_ssm_client(settings: type[BaseSettings]): # type: ignore[no-untyped- SSMClient: A parameter ssm boto3 client. """ logger.debug("Extracting settings prefixed with aws_") - args: dict[str, Any] = { + args: Dict[str, Any] = { k: v for k, v in settings.model_config.items() if k.startswith("aws_") } diff --git a/pydantic_settings_aws/settings.py b/pydantic_settings_aws/settings.py index 3c063e4..3928c04 100644 --- a/pydantic_settings_aws/settings.py +++ b/pydantic_settings_aws/settings.py @@ -1,3 +1,5 @@ +from typing import Tuple, Type + from pydantic_settings import ( BaseSettings, PydanticBaseSettingsSource, @@ -10,12 +12,12 @@ class ParameterStoreBaseSettings(BaseSettings): @classmethod def settings_customise_sources( cls, - settings_cls: type[BaseSettings], + settings_cls: Type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource, - ) -> tuple[PydanticBaseSettingsSource, ...]: + ) -> Tuple[PydanticBaseSettingsSource, ...]: return ( init_settings, ParameterStoreSettingsSource(settings_cls), @@ -29,12 +31,12 @@ class SecretsManagerBaseSettings(BaseSettings): @classmethod def settings_customise_sources( cls, - settings_cls: type[BaseSettings], + settings_cls: Type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource, - ) -> tuple[PydanticBaseSettingsSource, ...]: + ) -> Tuple[PydanticBaseSettingsSource, ...]: return ( init_settings, SecretsManagerSettingsSource(settings_cls), diff --git a/pydantic_settings_aws/sources.py b/pydantic_settings_aws/sources.py index fe340da..f5fb038 100644 --- a/pydantic_settings_aws/sources.py +++ b/pydantic_settings_aws/sources.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Dict, Tuple, Type from pydantic.fields import FieldInfo from pydantic_settings import ( @@ -12,12 +12,12 @@ class ParameterStoreSettingsSource(PydanticBaseSettingsSource): """Source class for loading settings from AWS Parameter Store. """ - def __init__(self, settings_cls: type[BaseSettings]): + def __init__(self, settings_cls: Type[BaseSettings]): super().__init__(settings_cls) def get_field_value( self, field: FieldInfo, field_name: str - ) -> tuple[Any, str, bool]: + ) -> Tuple[Any, str, bool]: ssm_info = utils.get_ssm_name_from_annotated_field(field.metadata) field_value = aws.get_ssm_content(self.settings_cls, field_name, ssm_info) @@ -32,8 +32,8 @@ def prepare_field_value( ) -> Any: return value - def __call__(self) -> dict[str, Any]: - d: dict[str, Any] = {} + def __call__(self) -> Dict[str, Any]: + d: Dict[str, Any] = {} for field_name, field in self.settings_cls.model_fields.items(): field_value, field_key, value_is_complex = self.get_field_value( @@ -49,13 +49,13 @@ def __call__(self) -> dict[str, Any]: class SecretsManagerSettingsSource(PydanticBaseSettingsSource): - def __init__(self, settings_cls: type[BaseSettings]): + def __init__(self, settings_cls: Type[BaseSettings]): super().__init__(settings_cls) self._json_content = aws.get_secrets_content(settings_cls) def get_field_value( self, field: FieldInfo, field_name: str - ) -> tuple[Any, str, bool]: + ) -> Tuple[Any, str, bool]: field_value = self._json_content.get(field_name) return field_value, field_name, False @@ -68,8 +68,8 @@ def prepare_field_value( ) -> Any: return value - def __call__(self) -> dict[str, Any]: - d: dict[str, Any] = {} + def __call__(self) -> Dict[str, Any]: + d: Dict[str, Any] = {} for field_name, field in self.settings_cls.model_fields.items(): field_value, field_key, value_is_complex = self.get_field_value( diff --git a/pyproject.toml b/pyproject.toml index 43e155c..4468a61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ classifiers = [ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -34,7 +35,7 @@ classifiers = [ 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Internet', ] -requires-python = '>=3.9' +requires-python = '>=3.8' dependencies = [ 'pydantic>=2.0.1', 'pydantic-settings>=2.0.2', @@ -85,7 +86,7 @@ quote-style = 'double' indent-style = 'space' [tool.mypy] -python_version = '3.10' +python_version = '3.8' show_error_codes = true follow_imports = 'silent' strict_optional = true diff --git a/requirements-test.txt b/requirements-test.txt index 9b6baa9..e88d628 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -3,6 +3,6 @@ black>=24.4.2 ruff>=0.5.1 mypy>=1.10.1 -pre-commit>=3.7.1 +pre-commit>=3.5.0 pytest>=8.2.2 pytest-cov>=5.0.0 diff --git a/tests/settings_mocks.py b/tests/settings_mocks.py index ea5038c..3875c1a 100644 --- a/tests/settings_mocks.py +++ b/tests/settings_mocks.py @@ -1,8 +1,9 @@ import json -from typing import Annotated, Optional +from typing import List, Optional from pydantic import BaseModel from pydantic_settings import SettingsConfigDict +from typing_extensions import Annotated from pydantic_settings_aws import ( ParameterStoreBaseSettings, @@ -45,7 +46,7 @@ class MySecretsWithClientConfig(SecretsManagerBaseSettings): class NestedContent(BaseModel): - roles: list[str] + roles: List[str] class SecretsWithNestedContent(SecretsManagerBaseSettings):