Skip to content

Commit ee1b8f5

Browse files
committed
chore(deps-dev): enable mypy-boto3 implicit type annotations
Add boto3-stubs and botocore-stubs to enable implicit type annotations with mypy_boto3_* packages. See https://youtype.github.io/boto3_stubs_docs/mypy_boto3_s3/usage/#implicit-type-annotations This enables removing very few unneeded explicit type annotations and helps detect some new errors automatically. No need to do boto3_config = boto3_config or Config() before passing the boto3_config to the boto3_session client or resource methods, as None is an accepted value. Refactor the aws_lambda_powertools.utilities.parameters.base.BaseProvider class: move the instantiation of clients in _build_boto3_client and _build_boto3_resource_client to the subclasses and the user_agent method calls to the constructor. Keeping correct typing information with the original implementation was going to be too complex and hopefully the refactor even made the code tidier. Keep imports consistent: use boto3.session.Session, mypy_boto3_*.client, mypy_boto3_*.service_resource and mypy_boto3_*.type_defs as documented at https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html and https://youtype.github.io/boto3_stubs_docs/mypy_boto3_s3/usage/#explicit-type-annotations
1 parent 46473a1 commit ee1b8f5

File tree

24 files changed

+537
-243
lines changed

24 files changed

+537
-243
lines changed

aws_lambda_powertools/utilities/data_classes/s3_object_event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class S3ObjectLambdaEvent(DictWrapper):
262262
import requests
263263
from aws_lambda_powertools.utilities.data_classes.s3_object_event import S3ObjectLambdaEvent
264264
265-
session = boto3.Session()
265+
session = boto3.session.Session()
266266
s3 = session.client("s3")
267267
268268
def lambda_handler(event, context):

aws_lambda_powertools/utilities/idempotency/persistence/dynamodb.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424

2525
if TYPE_CHECKING:
26-
from mypy_boto3_dynamodb import DynamoDBClient
26+
from mypy_boto3_dynamodb.client import DynamoDBClient
2727
from mypy_boto3_dynamodb.type_defs import AttributeValueTypeDef
2828

2929
logger = logging.getLogger(__name__)
@@ -43,7 +43,7 @@ def __init__(
4343
validation_key_attr: str = "validation",
4444
boto_config: Optional[Config] = None,
4545
boto3_session: Optional[boto3.session.Session] = None,
46-
boto3_client: "DynamoDBClient" | None = None,
46+
boto3_client: Optional[DynamoDBClient] = None,
4747
):
4848
"""
4949
Initialize the DynamoDB client
@@ -71,7 +71,7 @@ def __init__(
7171
DynamoDB attribute name for hashed representation of the parts of the event used for validation
7272
boto_config: botocore.config.Config, optional
7373
Botocore configuration to pass during client initialization
74-
boto3_session : boto3.Session, optional
74+
boto3_session : boto3.session.Session, optional
7575
Boto3 session to use for AWS API communication
7676
boto3_client : DynamoDBClient, optional
7777
Boto3 DynamoDB Client to use, boto3_session and boto_config will be ignored if both are provided
@@ -91,11 +91,8 @@ def __init__(
9191
>>> return {"StatusCode": 200}
9292
"""
9393
if boto3_client is None:
94-
self._boto_config = boto_config or Config()
95-
self._boto3_session: boto3.Session = boto3_session or boto3.session.Session()
96-
self.client: "DynamoDBClient" = self._boto3_session.client("dynamodb", config=self._boto_config)
97-
else:
98-
self.client = boto3_client
94+
boto3_client = (boto3_session or boto3.session.Session()).client("dynamodb", config=boto_config)
95+
self.client = boto3_client
9996

10097
user_agent.register_feature_to_client(client=self.client, feature="idempotency")
10198

@@ -297,7 +294,7 @@ def boto3_supports_condition_check_failure(boto3_version: str) -> bool:
297294
def _update_record(self, data_record: DataRecord):
298295
logger.debug(f"Updating record for idempotency key: {data_record.idempotency_key}")
299296
update_expression = "SET #response_data = :response_data, #expiry = :expiry, #status = :status"
300-
expression_attr_values: Dict[str, "AttributeValueTypeDef"] = {
297+
expression_attr_values: Dict[str, AttributeValueTypeDef] = {
301298
":expiry": {"N": str(data_record.expiry_timestamp)},
302299
":response_data": {"S": data_record.response_data},
303300
":status": {"S": data_record.status},

aws_lambda_powertools/utilities/parameters/appconfig.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
"""
44

55
import os
6-
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
6+
from typing import TYPE_CHECKING, Dict, Optional, Union
77

88
import boto3
99
from botocore.config import Config
1010

1111
from aws_lambda_powertools.utilities.parameters.types import TransformOptions
1212

1313
if TYPE_CHECKING:
14-
from mypy_boto3_appconfigdata import AppConfigDataClient
14+
from mypy_boto3_appconfigdata.client import AppConfigDataClient
1515

1616
from aws_lambda_powertools.shared import constants
1717
from aws_lambda_powertools.shared.functions import (
@@ -67,8 +67,6 @@ class AppConfigProvider(BaseProvider):
6767
6868
"""
6969

70-
client: Any = None
71-
7270
def __init__(
7371
self,
7472
environment: str,
@@ -80,15 +78,9 @@ def __init__(
8078
"""
8179
Initialize the App Config client
8280
"""
83-
84-
super().__init__()
85-
86-
self.client: "AppConfigDataClient" = self._build_boto3_client(
87-
service_name="appconfigdata",
88-
client=boto3_client,
89-
session=boto3_session,
90-
config=config,
91-
)
81+
if boto3_client is None:
82+
boto3_client = (boto3_session or boto3.session.Session()).client("appconfigdata", config=config)
83+
self.client = boto3_client
9284

9385
self.application = resolve_env_var_choice(
9486
choice=application,
@@ -99,9 +91,11 @@ def __init__(
9991

10092
self._next_token: Dict[str, str] = {} # nosec - token for get_latest_configuration executions
10193
# Dict to store the recently retrieved value for a specific configuration.
102-
self.last_returned_value: Dict[str, str] = {}
94+
self.last_returned_value: Dict[str, bytes] = {}
95+
96+
super().__init__(client=self.client)
10397

104-
def _get(self, name: str, **sdk_options) -> str:
98+
def _get(self, name: str, **sdk_options) -> bytes:
10599
"""
106100
Retrieve a parameter value from AWS App config.
107101

aws_lambda_powertools/utilities/parameters/base.py

Lines changed: 5 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,30 @@
1010
from abc import ABC, abstractmethod
1111
from datetime import datetime, timedelta
1212
from typing import (
13-
TYPE_CHECKING,
1413
Any,
1514
Callable,
1615
Dict,
1716
NamedTuple,
1817
Optional,
1918
Tuple,
20-
Type,
2119
Union,
2220
cast,
2321
overload,
2422
)
2523

26-
import boto3
27-
from botocore.config import Config
28-
2924
from aws_lambda_powertools.shared import constants, user_agent
3025
from aws_lambda_powertools.shared.functions import resolve_max_age
3126
from aws_lambda_powertools.utilities.parameters.types import TransformOptions
3227

3328
from .exceptions import GetParameterError, TransformParameterError
3429

35-
if TYPE_CHECKING:
36-
from mypy_boto3_appconfigdata import AppConfigDataClient
37-
from mypy_boto3_dynamodb import DynamoDBServiceResource
38-
from mypy_boto3_secretsmanager import SecretsManagerClient
39-
from mypy_boto3_ssm import SSMClient
40-
41-
4230
DEFAULT_MAX_AGE_SECS = "300"
4331

4432
# These providers will be dynamically initialized on first use of the helper functions
4533
DEFAULT_PROVIDERS: Dict[str, Any] = {}
4634
TRANSFORM_METHOD_JSON = "json"
4735
TRANSFORM_METHOD_BINARY = "binary"
4836
SUPPORTED_TRANSFORM_METHODS = [TRANSFORM_METHOD_JSON, TRANSFORM_METHOD_BINARY]
49-
ParameterClients = Union["AppConfigDataClient", "SecretsManagerClient", "SSMClient"]
5037

5138
TRANSFORM_METHOD_MAPPING = {
5239
TRANSFORM_METHOD_JSON: json.loads,
@@ -69,10 +56,14 @@ class BaseProvider(ABC):
6956

7057
store: Dict[Tuple, ExpirableValue]
7158

72-
def __init__(self):
59+
def __init__(self, *, client=None, resource=None):
7360
"""
7461
Initialize the base provider
7562
"""
63+
if client is not None:
64+
user_agent.register_feature_to_client(client=client, feature="parameters")
65+
if resource is not None:
66+
user_agent.register_feature_to_resource(resource=resource, feature="parameters")
7667

7768
self.store: Dict[Tuple, ExpirableValue] = {}
7869

@@ -262,78 +253,6 @@ def _build_cache_key(
262253
"""
263254
return (name, transform, is_nested)
264255

265-
@staticmethod
266-
def _build_boto3_client(
267-
service_name: str,
268-
client: Optional[ParameterClients] = None,
269-
session: Optional[Type[boto3.Session]] = None,
270-
config: Optional[Type[Config]] = None,
271-
) -> Type[ParameterClients]:
272-
"""Builds a low level boto3 client with session and config provided
273-
274-
Parameters
275-
----------
276-
service_name : str
277-
AWS service name to instantiate a boto3 client, e.g. ssm
278-
client : Optional[ParameterClients], optional
279-
boto3 client instance, by default None
280-
session : Optional[Type[boto3.Session]], optional
281-
boto3 session instance, by default None
282-
config : Optional[Type[Config]], optional
283-
botocore config instance to configure client with, by default None
284-
285-
Returns
286-
-------
287-
Type[ParameterClients]
288-
Instance of a boto3 client for Parameters feature (e.g., ssm, appconfig, secretsmanager, etc.)
289-
"""
290-
if client is not None:
291-
user_agent.register_feature_to_client(client=client, feature="parameters")
292-
return client
293-
294-
session = session or boto3.Session()
295-
config = config or Config()
296-
client = session.client(service_name=service_name, config=config)
297-
user_agent.register_feature_to_client(client=client, feature="parameters")
298-
return client
299-
300-
# maintenance: change DynamoDBServiceResource type to ParameterResourceClients when we expand
301-
@staticmethod
302-
def _build_boto3_resource_client(
303-
service_name: str,
304-
client: Optional["DynamoDBServiceResource"] = None,
305-
session: Optional[Type[boto3.Session]] = None,
306-
config: Optional[Type[Config]] = None,
307-
endpoint_url: Optional[str] = None,
308-
) -> "DynamoDBServiceResource":
309-
"""Builds a high level boto3 resource client with session, config and endpoint_url provided
310-
311-
Parameters
312-
----------
313-
service_name : str
314-
AWS service name to instantiate a boto3 client, e.g. ssm
315-
client : Optional[DynamoDBServiceResource], optional
316-
boto3 client instance, by default None
317-
session : Optional[Type[boto3.Session]], optional
318-
boto3 session instance, by default None
319-
config : Optional[Type[Config]], optional
320-
botocore config instance to configure client, by default None
321-
322-
Returns
323-
-------
324-
Type[DynamoDBServiceResource]
325-
Instance of a boto3 resource client for Parameters feature (e.g., dynamodb, etc.)
326-
"""
327-
if client is not None:
328-
user_agent.register_feature_to_resource(resource=client, feature="parameters")
329-
return client
330-
331-
session = session or boto3.Session()
332-
config = config or Config()
333-
client = session.resource(service_name=service_name, config=config, endpoint_url=endpoint_url)
334-
user_agent.register_feature_to_resource(resource=client, feature="parameters")
335-
return client
336-
337256

338257
def get_transform_method(value: str, transform: TransformOptions = None) -> Callable[..., Any]:
339258
"""

aws_lambda_powertools/utilities/parameters/dynamodb.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
from .base import BaseProvider
1212

1313
if TYPE_CHECKING:
14-
from mypy_boto3_dynamodb import DynamoDBServiceResource
15-
from mypy_boto3_dynamodb.service_resource import Table
14+
from mypy_boto3_dynamodb.service_resource import DynamoDBServiceResource
1615

1716

1817
class DynamoDBProvider(BaseProvider):
@@ -162,19 +161,15 @@ def __init__(
162161
"""
163162
Initialize the DynamoDB client
164163
"""
165-
self.table: "Table" = self._build_boto3_resource_client(
166-
service_name="dynamodb",
167-
client=boto3_client,
168-
session=boto3_session,
169-
config=config,
170-
endpoint_url=endpoint_url,
171-
).Table(table_name)
172-
164+
if boto3_client is None:
165+
boto3_session = boto3_session or boto3.session.Session()
166+
boto3_client = boto3_session.resource("dynamodb", config=config, endpoint_url=endpoint_url)
167+
self.table = boto3_client.Table(table_name)
173168
self.key_attr = key_attr
174169
self.sort_attr = sort_attr
175170
self.value_attr = value_attr
176171

177-
super().__init__()
172+
super().__init__(resource=boto3_client)
178173

179174
def _get(self, name: str, **sdk_options) -> str:
180175
"""

aws_lambda_powertools/utilities/parameters/secrets.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@
77
import json
88
import logging
99
import os
10-
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union, overload
10+
from typing import TYPE_CHECKING, Dict, Literal, Optional, Union, overload
1111

1212
import boto3
1313
from botocore.config import Config
1414

1515
if TYPE_CHECKING:
16-
from mypy_boto3_secretsmanager import SecretsManagerClient
16+
from mypy_boto3_secretsmanager.client import SecretsManagerClient
17+
from mypy_boto3_secretsmanager.type_defs import CreateSecretResponseTypeDef
1718

1819
from aws_lambda_powertools.shared import constants
1920
from aws_lambda_powertools.shared.functions import resolve_max_age
2021
from aws_lambda_powertools.shared.json_encoder import Encoder
2122
from aws_lambda_powertools.utilities.parameters.base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider
2223
from aws_lambda_powertools.utilities.parameters.exceptions import SetSecretError
23-
from aws_lambda_powertools.utilities.parameters.types import SetSecretResponse, TransformOptions
24+
from aws_lambda_powertools.utilities.parameters.types import TransformOptions
2425

2526
logger = logging.getLogger(__name__)
2627

@@ -74,28 +75,22 @@ class SecretsProvider(BaseProvider):
7475
My parameter value
7576
"""
7677

77-
client: Any = None
78-
7978
def __init__(
8079
self,
8180
config: Optional[Config] = None,
8281
boto3_session: Optional[boto3.session.Session] = None,
83-
boto3_client: Optional["SecretsManagerClient"] = None,
82+
boto3_client: Optional[SecretsManagerClient] = None,
8483
):
8584
"""
8685
Initialize the Secrets Manager client
8786
"""
87+
if boto3_client is None:
88+
boto3_client = (boto3_session or boto3.session.Session()).client("secretsmanager", config=config)
89+
self.client = boto3_client
8890

89-
super().__init__()
90-
91-
self.client: "SecretsManagerClient" = self._build_boto3_client(
92-
service_name="secretsmanager",
93-
client=boto3_client,
94-
session=boto3_session,
95-
config=config,
96-
)
91+
super().__init__(client=self.client)
9792

98-
def _get(self, name: str, **sdk_options) -> str:
93+
def _get(self, name: str, **sdk_options) -> Union[str, bytes]:
9994
"""
10095
Retrieve a parameter value from AWS Systems Manager Parameter Store
10196
@@ -123,7 +118,7 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
123118
"""
124119
raise NotImplementedError()
125120

126-
def _create_secret(self, name: str, **sdk_options):
121+
def _create_secret(self, name: str, **sdk_options) -> CreateSecretResponseTypeDef:
127122
"""
128123
Create a secret with the given name.
129124
@@ -164,7 +159,7 @@ def set(
164159
*, # force keyword arguments
165160
client_request_token: Optional[str] = None,
166161
**sdk_options,
167-
) -> SetSecretResponse:
162+
) -> CreateSecretResponseTypeDef:
168163
"""
169164
Modify the details of a secret or create a new secret if it doesn't already exist.
170165
@@ -366,7 +361,7 @@ def set_secret(
366361
*, # force keyword arguments
367362
client_request_token: Optional[str] = None,
368363
**sdk_options,
369-
) -> SetSecretResponse:
364+
) -> CreateSecretResponseTypeDef:
370365
"""
371366
Modify the details of a secret or create a new secret if it doesn't already exist.
372367

0 commit comments

Comments
 (0)