Skip to content

Commit 5857104

Browse files
committed
Merge branch 'develop' into docs/new-maintainers
* develop: chore: bump to 1.26.0 chore(deps-dev): bump mypy-boto3-secretsmanager from 1.21.34 to 1.23.0.post1 (aws-powertools#1218) chore(deps-dev): bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 (aws-powertools#1219) chore(deps-dev): bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 (aws-powertools#1220) chore(deps): bump pydantic from 1.9.0 to 1.9.1 (aws-powertools#1221) feat(parameters): accept boto3_client to support private endpoints and ease testing (aws-powertools#1096) fix(docs): remove Slack link (aws-powertools#1210)
2 parents dd519f9 + 0704670 commit 5857104

File tree

11 files changed

+507
-114
lines changed

11 files changed

+507
-114
lines changed

CHANGELOG.md

+25
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
44

55
This project follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format for changes and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## 1.26.0 - 2022-05-20
8+
9+
### Bug Fixes
10+
11+
* **batch:** missing space in BatchProcessingError message ([#1201](https://github.com/awslabs/aws-lambda-powertools-python/issues/1201))
12+
* **batch:** docstring fix for success_handler() record parameter ([#1202](https://github.com/awslabs/aws-lambda-powertools-python/issues/1202))
13+
* **docs:** remove Slack link ([#1210](https://github.com/awslabs/aws-lambda-powertools-python/issues/1210))
14+
15+
### Documentation
16+
17+
* **layer:** upgrade to 1.25.10
18+
* **roadmap:** add new roadmap section ([#1204](https://github.com/awslabs/aws-lambda-powertools-python/issues/1204))
19+
20+
### Features
21+
22+
* **parameters:** accept boto3_client to support private endpoints and ease testing ([#1096](https://github.com/awslabs/aws-lambda-powertools-python/issues/1096))
23+
24+
### Maintenance
25+
26+
* **deps:** bump pydantic from 1.9.0 to 1.9.1 ([#1221](https://github.com/awslabs/aws-lambda-powertools-python/issues/1221))
27+
* **deps:** bump email-validator from 1.1.3 to 1.2.1 ([#1199](https://github.com/awslabs/aws-lambda-powertools-python/issues/1199))
28+
* **deps-dev:** bump mypy-boto3-secretsmanager from 1.21.34 to 1.23.0.post1 ([#1218](https://github.com/awslabs/aws-lambda-powertools-python/issues/1218))
29+
* **deps-dev:** bump mypy-boto3-appconfig from 1.21.34 to 1.23.0.post1 ([#1219](https://github.com/awslabs/aws-lambda-powertools-python/issues/1219))
30+
* **deps-dev:** bump mypy-boto3-ssm from 1.21.34 to 1.23.0.post1 ([#1220](https://github.com/awslabs/aws-lambda-powertools-python/issues/1220))
31+
732
## 1.25.10 - 2022-04-29
833

934
### Bug Fixes

README.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip insta
4747
* Powertools idea [DAZN Powertools](https://github.com/getndazn/dazn-lambda-powertools/)
4848

4949
## Connect
50-
51-
* **AWS Developers Slack**: `#lambda-powertools` - **[Invite, if you don't have an account](https://join.slack.com/t/awsdevelopers/shared_invite/zt-yryddays-C9fkWrmguDv0h2EEDzCqvw)**
52-
* **Email**: [email protected]
50+
5351

5452
## Security disclosures
5553

aws_lambda_powertools/utilities/parameters/appconfig.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55

66
import os
7-
from typing import Any, Dict, Optional, Union
7+
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
88
from uuid import uuid4
99

1010
import boto3
1111
from botocore.config import Config
1212

13+
if TYPE_CHECKING:
14+
from mypy_boto3_appconfig import AppConfigClient
15+
1316
from ...shared import constants
1417
from ...shared.functions import resolve_env_var_choice
1518
from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider
@@ -30,7 +33,9 @@ class AppConfigProvider(BaseProvider):
3033
config: botocore.config.Config, optional
3134
Botocore configuration to pass during client initialization
3235
boto3_session : boto3.session.Session, optional
33-
Boto3 session to use for AWS API communication
36+
Boto3 session to create a boto3_client from
37+
boto3_client: AppConfigClient, optional
38+
Boto3 AppConfig Client to use, boto3_session will be ignored if both are provided
3439
3540
Example
3641
-------
@@ -68,22 +73,24 @@ def __init__(
6873
application: Optional[str] = None,
6974
config: Optional[Config] = None,
7075
boto3_session: Optional[boto3.session.Session] = None,
76+
boto3_client: Optional["AppConfigClient"] = None,
7177
):
7278
"""
7379
Initialize the App Config client
7480
"""
7581

76-
config = config or Config()
77-
session = boto3_session or boto3.session.Session()
78-
self.client = session.client("appconfig", config=config)
82+
super().__init__()
83+
84+
self.client: "AppConfigClient" = self._build_boto3_client(
85+
service_name="appconfig", client=boto3_client, session=boto3_session, config=config
86+
)
87+
7988
self.application = resolve_env_var_choice(
8089
choice=application, env=os.getenv(constants.SERVICE_NAME_ENV, "service_undefined")
8190
)
8291
self.environment = environment
8392
self.current_version = ""
8493

85-
super().__init__()
86-
8794
def _get(self, name: str, **sdk_options) -> str:
8895
"""
8996
Retrieve a parameter value from AWS App config.

aws_lambda_powertools/utilities/parameters/base.py

+78-1
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,28 @@
77
from abc import ABC, abstractmethod
88
from collections import namedtuple
99
from datetime import datetime, timedelta
10-
from typing import Any, Dict, Optional, Tuple, Union
10+
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type, Union
11+
12+
import boto3
13+
from botocore.config import Config
1114

1215
from .exceptions import GetParameterError, TransformParameterError
1316

17+
if TYPE_CHECKING:
18+
from mypy_boto3_appconfig import AppConfigClient
19+
from mypy_boto3_dynamodb import DynamoDBServiceResource
20+
from mypy_boto3_secretsmanager import SecretsManagerClient
21+
from mypy_boto3_ssm import SSMClient
22+
23+
1424
DEFAULT_MAX_AGE_SECS = 5
1525
ExpirableValue = namedtuple("ExpirableValue", ["value", "ttl"])
1626
# These providers will be dynamically initialized on first use of the helper functions
1727
DEFAULT_PROVIDERS: Dict[str, Any] = {}
1828
TRANSFORM_METHOD_JSON = "json"
1929
TRANSFORM_METHOD_BINARY = "binary"
2030
SUPPORTED_TRANSFORM_METHODS = [TRANSFORM_METHOD_JSON, TRANSFORM_METHOD_BINARY]
31+
ParameterClients = Union["AppConfigClient", "SecretsManagerClient", "SSMClient"]
2132

2233

2334
class BaseProvider(ABC):
@@ -180,6 +191,72 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
180191
def clear_cache(self):
181192
self.store.clear()
182193

194+
@staticmethod
195+
def _build_boto3_client(
196+
service_name: str,
197+
client: Optional[ParameterClients] = None,
198+
session: Optional[Type[boto3.Session]] = None,
199+
config: Optional[Type[Config]] = None,
200+
) -> Type[ParameterClients]:
201+
"""Builds a low level boto3 client with session and config provided
202+
203+
Parameters
204+
----------
205+
service_name : str
206+
AWS service name to instantiate a boto3 client, e.g. ssm
207+
client : Optional[ParameterClients], optional
208+
boto3 client instance, by default None
209+
session : Optional[Type[boto3.Session]], optional
210+
boto3 session instance, by default None
211+
config : Optional[Type[Config]], optional
212+
botocore config instance to configure client with, by default None
213+
214+
Returns
215+
-------
216+
Type[ParameterClients]
217+
Instance of a boto3 client for Parameters feature (e.g., ssm, appconfig, secretsmanager, etc.)
218+
"""
219+
if client is not None:
220+
return client
221+
222+
session = session or boto3.Session()
223+
config = config or Config()
224+
return session.client(service_name=service_name, config=config)
225+
226+
# maintenance: change DynamoDBServiceResource type to ParameterResourceClients when we expand
227+
@staticmethod
228+
def _build_boto3_resource_client(
229+
service_name: str,
230+
client: Optional["DynamoDBServiceResource"] = None,
231+
session: Optional[Type[boto3.Session]] = None,
232+
config: Optional[Type[Config]] = None,
233+
endpoint_url: Optional[str] = None,
234+
) -> "DynamoDBServiceResource":
235+
"""Builds a high level boto3 resource client with session, config and endpoint_url provided
236+
237+
Parameters
238+
----------
239+
service_name : str
240+
AWS service name to instantiate a boto3 client, e.g. ssm
241+
client : Optional[DynamoDBServiceResource], optional
242+
boto3 client instance, by default None
243+
session : Optional[Type[boto3.Session]], optional
244+
boto3 session instance, by default None
245+
config : Optional[Type[Config]], optional
246+
botocore config instance to configure client, by default None
247+
248+
Returns
249+
-------
250+
Type[DynamoDBServiceResource]
251+
Instance of a boto3 resource client for Parameters feature (e.g., dynamodb, etc.)
252+
"""
253+
if client is not None:
254+
return client
255+
256+
session = session or boto3.Session()
257+
config = config or Config()
258+
return session.resource(service_name=service_name, config=config, endpoint_url=endpoint_url)
259+
183260

184261
def get_transform_method(key: str, transform: Optional[str] = None) -> Optional[str]:
185262
"""

aws_lambda_powertools/utilities/parameters/dynamodb.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
"""
44

55

6-
from typing import Dict, Optional
6+
from typing import TYPE_CHECKING, Dict, Optional
77

88
import boto3
99
from boto3.dynamodb.conditions import Key
1010
from botocore.config import Config
1111

1212
from .base import BaseProvider
1313

14+
if TYPE_CHECKING:
15+
from mypy_boto3_dynamodb import DynamoDBServiceResource
16+
from mypy_boto3_dynamodb.service_resource import Table
17+
1418

1519
class DynamoDBProvider(BaseProvider):
1620
"""
@@ -31,7 +35,9 @@ class DynamoDBProvider(BaseProvider):
3135
config: botocore.config.Config, optional
3236
Botocore configuration to pass during client initialization
3337
boto3_session : boto3.session.Session, optional
34-
Boto3 session to use for AWS API communication
38+
Boto3 session to create a boto3_client from
39+
boto3_client: DynamoDBServiceResource, optional
40+
Boto3 DynamoDB Resource Client to use; boto3_session will be ignored if both are provided
3541
3642
Example
3743
-------
@@ -152,15 +158,18 @@ def __init__(
152158
endpoint_url: Optional[str] = None,
153159
config: Optional[Config] = None,
154160
boto3_session: Optional[boto3.session.Session] = None,
161+
boto3_client: Optional["DynamoDBServiceResource"] = None,
155162
):
156163
"""
157164
Initialize the DynamoDB client
158165
"""
159-
160-
config = config or Config()
161-
session = boto3_session or boto3.session.Session()
162-
163-
self.table = session.resource("dynamodb", endpoint_url=endpoint_url, config=config).Table(table_name)
166+
self.table: "Table" = self._build_boto3_resource_client(
167+
service_name="dynamodb",
168+
client=boto3_client,
169+
session=boto3_session,
170+
config=config,
171+
endpoint_url=endpoint_url,
172+
).Table(table_name)
164173

165174
self.key_attr = key_attr
166175
self.sort_attr = sort_attr
@@ -183,7 +192,9 @@ def _get(self, name: str, **sdk_options) -> str:
183192
# Explicit arguments will take precedence over keyword arguments
184193
sdk_options["Key"] = {self.key_attr: name}
185194

186-
return self.table.get_item(**sdk_options)["Item"][self.value_attr]
195+
# maintenance: look for better ways to correctly type DynamoDB multiple return types
196+
# without a breaking change within ABC return type
197+
return self.table.get_item(**sdk_options)["Item"][self.value_attr] # type: ignore[return-value]
187198

188199
def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
189200
"""
@@ -209,4 +220,6 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
209220
response = self.table.query(**sdk_options)
210221
items.extend(response.get("Items", []))
211222

212-
return {item[self.sort_attr]: item[self.value_attr] for item in items}
223+
# maintenance: look for better ways to correctly type DynamoDB multiple return types
224+
# without a breaking change within ABC return type
225+
return {item[self.sort_attr]: item[self.value_attr] for item in items} # type: ignore[misc]

aws_lambda_powertools/utilities/parameters/secrets.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
"""
44

55

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

88
import boto3
99
from botocore.config import Config
1010

11+
if TYPE_CHECKING:
12+
from mypy_boto3_secretsmanager import SecretsManagerClient
13+
1114
from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider
1215

1316

@@ -20,7 +23,9 @@ class SecretsProvider(BaseProvider):
2023
config: botocore.config.Config, optional
2124
Botocore configuration to pass during client initialization
2225
boto3_session : boto3.session.Session, optional
23-
Boto3 session to use for AWS API communication
26+
Boto3 session to create a boto3_client from
27+
boto3_client: SecretsManagerClient, optional
28+
Boto3 SecretsManager Client to use, boto3_session will be ignored if both are provided
2429
2530
Example
2631
-------
@@ -60,17 +65,22 @@ class SecretsProvider(BaseProvider):
6065

6166
client: Any = None
6267

63-
def __init__(self, config: Optional[Config] = None, boto3_session: Optional[boto3.session.Session] = None):
68+
def __init__(
69+
self,
70+
config: Optional[Config] = None,
71+
boto3_session: Optional[boto3.session.Session] = None,
72+
boto3_client: Optional["SecretsManagerClient"] = None,
73+
):
6474
"""
6575
Initialize the Secrets Manager client
6676
"""
6777

68-
config = config or Config()
69-
session = boto3_session or boto3.session.Session()
70-
self.client = session.client("secretsmanager", config=config)
71-
7278
super().__init__()
7379

80+
self.client: "SecretsManagerClient" = self._build_boto3_client(
81+
service_name="secretsmanager", client=boto3_client, session=boto3_session, config=config
82+
)
83+
7484
def _get(self, name: str, **sdk_options) -> str:
7585
"""
7686
Retrieve a parameter value from AWS Systems Manager Parameter Store

aws_lambda_powertools/utilities/parameters/ssm.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
"""
44

55

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

88
import boto3
99
from botocore.config import Config
1010

1111
from .base import DEFAULT_MAX_AGE_SECS, DEFAULT_PROVIDERS, BaseProvider
1212

13+
if TYPE_CHECKING:
14+
from mypy_boto3_ssm import SSMClient
15+
1316

1417
class SSMProvider(BaseProvider):
1518
"""
@@ -20,7 +23,9 @@ class SSMProvider(BaseProvider):
2023
config: botocore.config.Config, optional
2124
Botocore configuration to pass during client initialization
2225
boto3_session : boto3.session.Session, optional
23-
Boto3 session to use for AWS API communication
26+
Boto3 session to create a boto3_client from
27+
boto3_client: SSMClient, optional
28+
Boto3 SSM Client to use, boto3_session will be ignored if both are provided
2429
2530
Example
2631
-------
@@ -76,17 +81,22 @@ class SSMProvider(BaseProvider):
7681

7782
client: Any = None
7883

79-
def __init__(self, config: Optional[Config] = None, boto3_session: Optional[boto3.session.Session] = None):
84+
def __init__(
85+
self,
86+
config: Optional[Config] = None,
87+
boto3_session: Optional[boto3.session.Session] = None,
88+
boto3_client: Optional["SSMClient"] = None,
89+
):
8090
"""
8191
Initialize the SSM Parameter Store client
8292
"""
8393

84-
config = config or Config()
85-
session = boto3_session or boto3.session.Session()
86-
self.client = session.client("ssm", config=config)
87-
8894
super().__init__()
8995

96+
self.client: "SSMClient" = self._build_boto3_client(
97+
service_name="ssm", client=boto3_client, session=boto3_session, config=config
98+
)
99+
90100
# We break Liskov substitution principle due to differences in signatures of this method and superclass get method
91101
# We ignore mypy error, as changes to the signature here or in a superclass is a breaking change to users
92102
def get( # type: ignore[override]

0 commit comments

Comments
 (0)