Skip to content

Commit 3f11f7b

Browse files
authored
Merge pull request #12 from AllenInstitute/more-robust-boto3-clients
More robust boto3 clients
2 parents d0dd3ca + 440f70c commit 3f11f7b

File tree

2 files changed

+101
-14
lines changed

2 files changed

+101
-14
lines changed

src/aibs_informatics_aws_utils/core.py

+32-14
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,8 @@
1111
]
1212
import logging
1313
import os
14-
import re
1514
from dataclasses import dataclass
16-
from typing import (
17-
TYPE_CHECKING,
18-
ClassVar,
19-
Generic,
20-
Literal,
21-
Optional,
22-
Pattern,
23-
TypeVar,
24-
Union,
25-
cast,
26-
)
15+
from typing import TYPE_CHECKING, Generic, Literal, Optional, TypeVar, Union, cast
2716

2817
import boto3
2918
from aibs_informatics_core.models.aws.core import AWSRegion
@@ -32,6 +21,7 @@
3221
from boto3 import Session
3322
from boto3.resources.base import ServiceResource
3423
from botocore.client import BaseClient, ClientError
24+
from botocore.config import Config
3525
from botocore.session import Session as BotocoreSession
3626

3727
if TYPE_CHECKING: # pragma: no cover
@@ -249,8 +239,22 @@ def get_client(
249239
region_name = get_region(region=region or kwargs.get("region_name"))
250240
if region_name:
251241
kwargs["region_name"] = region_name
242+
243+
# If config for our client is not set, we want to set it to use "standard" mode
244+
# (default is "legacy") and increase the number of retries to 5 (default is 3)
245+
# See: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#available-retry-modes
246+
config: Optional[Config] = kwargs.pop("config", None)
247+
default_config = Config(
248+
connect_timeout=120, read_timeout=120, retries={"max_attempts": 6, "mode": "standard"}
249+
)
250+
if config is None:
251+
config = default_config
252+
else:
253+
# Have values in pre-existing config (if it exists) take precedence over default_config
254+
config = default_config.merge(other_config=config)
255+
252256
session = session or boto3.Session()
253-
return session.client(service, **kwargs)
257+
return session.client(service, config=config, **kwargs)
254258

255259

256260
@cache
@@ -280,8 +284,22 @@ def get_resource(
280284
region_name = get_region(region=region or kwargs.get("region_name"))
281285
if region_name:
282286
kwargs["region_name"] = region_name
287+
288+
# If config for our client is not set, we want to set it to use "standard" mode
289+
# (default is "legacy") and increase the number of retries to 5 (default is 3)
290+
# See: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html#available-retry-modes
291+
config: Optional[Config] = kwargs.pop("config", None)
292+
default_config = Config(
293+
connect_timeout=120, read_timeout=120, retries={"max_attempts": 6, "mode": "standard"}
294+
)
295+
if config is None:
296+
config = default_config
297+
else:
298+
# Have values in pre-existing config (if it exists) take precedence over default_config
299+
config = default_config.merge(other_config=config)
300+
283301
session = session or boto3.Session()
284-
return session.resource(service, **kwargs)
302+
return session.resource(service, config=config, **kwargs)
285303

286304

287305
@dataclass

test/aibs_informatics_aws_utils/test_core.py

+69
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import moto
55
import pytest
6+
from botocore.config import Config
67

78
from aibs_informatics_aws_utils.core import (
89
AWSService,
@@ -116,3 +117,71 @@ def test__get_resource__gets_service_resources(self):
116117
mock.call("sqs", region=None),
117118
]
118119
)
120+
121+
122+
@pytest.mark.parametrize(
123+
"service, preexisting_config, expected_retries_config",
124+
[
125+
pytest.param(
126+
# service
127+
"s3",
128+
# preexisting_config
129+
None,
130+
# expected_retries_config
131+
{"total_max_attempts": 7, "mode": "standard"},
132+
id="Basic test case (no preexisting_config provided)",
133+
),
134+
pytest.param(
135+
# service
136+
"dynamodb",
137+
# preexisting_config
138+
Config(retries={"max_attempts": 8, "mode": "adaptive"}),
139+
# expected_retries_config
140+
{"total_max_attempts": 9, "mode": "adaptive"},
141+
id="Test preexisting_config doesn't get overridden by default",
142+
),
143+
],
144+
)
145+
def test___core__get_client__config_setup_properly(
146+
aws_credentials_fixture, service, preexisting_config, expected_retries_config
147+
):
148+
if preexisting_config:
149+
client = get_client(service=service, config=preexisting_config)
150+
else:
151+
client = get_client(service=service)
152+
153+
assert expected_retries_config == client._client_config.retries
154+
155+
156+
@pytest.mark.parametrize(
157+
"service, preexisting_config, expected_retries_config",
158+
[
159+
pytest.param(
160+
# service
161+
"s3",
162+
# preexisting_config
163+
None,
164+
# expected_retries_config
165+
{"total_max_attempts": 7, "mode": "standard"},
166+
id="Basic test case (no preexisting_config provided)",
167+
),
168+
pytest.param(
169+
# service
170+
"dynamodb",
171+
# preexisting_config
172+
Config(retries={"max_attempts": 8, "mode": "adaptive"}),
173+
# expected_retries_config
174+
{"total_max_attempts": 9, "mode": "adaptive"},
175+
id="Test preexisting_config doesn't get overridden by default",
176+
),
177+
],
178+
)
179+
def test___core__get_resource__config_setup_properly(
180+
aws_credentials_fixture, service, preexisting_config, expected_retries_config
181+
):
182+
if preexisting_config:
183+
resource = get_resource(service=service, config=preexisting_config)
184+
else:
185+
resource = get_resource(service=service)
186+
187+
assert expected_retries_config == resource.meta.client._client_config.retries

0 commit comments

Comments
 (0)