Skip to content

Commit 1a35466

Browse files
authored
Bump botocore dependency specification (#1307)
1 parent 295cc41 commit 1a35466

File tree

10 files changed

+579
-54
lines changed

10 files changed

+579
-54
lines changed

CHANGES.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
Changes
22
-------
33

4-
2.21.0 (2025-02-27)
4+
2.21.0 (2025-02-28)
55
^^^^^^^^^^^^^^^^^^^
66
* make `AioDeferredRefreshableCredentials` subclass of `DeferredRefreshableCredentials`
77
* make `AioSSOCredentialFetcher` subclass of `SSOCredentialFetcher`
8+
* bump botocore dependency specification
89

910
2.20.1.dev0 (2025-02-24)
1011
^^^^^^^^^^^^^^^^^^^^^^^^

aiobotocore/args.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def get_client_args(
4949
configured_endpoint_url = final_args['configured_endpoint_url']
5050
signing_region = endpoint_config['signing_region']
5151
endpoint_region_name = endpoint_config['region_name']
52+
account_id_endpoint_mode = config_kwargs['account_id_endpoint_mode']
5253

5354
event_emitter = copy.copy(self._event_emitter)
5455
signer = AioRequestSigner(
@@ -107,6 +108,8 @@ def get_client_args(
107108
is_secure,
108109
endpoint_bridge,
109110
event_emitter,
111+
credentials,
112+
account_id_endpoint_mode,
110113
)
111114

112115
# Copy the session's user agent factory and adds client configuration.
@@ -144,6 +147,8 @@ def _build_endpoint_resolver(
144147
is_secure,
145148
endpoint_bridge,
146149
event_emitter,
150+
credentials,
151+
account_id_endpoint_mode,
147152
):
148153
if endpoints_ruleset_data is None:
149154
return None
@@ -168,6 +173,8 @@ def _build_endpoint_resolver(
168173
endpoint_bridge=endpoint_bridge,
169174
client_endpoint_url=endpoint_url,
170175
legacy_endpoint_url=endpoint.host,
176+
credentials=credentials,
177+
account_id_endpoint_mode=account_id_endpoint_mode,
171178
)
172179
# Client context params for s3 conflict with the available settings
173180
# in the `s3` parameter on the `Config` object. If the same parameter

aiobotocore/credentials.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ async def call(self):
249249
class AioCredentials(Credentials):
250250
async def get_frozen_credentials(self):
251251
return ReadOnlyCredentials(
252-
self.access_key, self.secret_key, self.token
252+
self.access_key, self.secret_key, self.token, self.account_id
253253
)
254254

255255

@@ -299,6 +299,19 @@ def token(self):
299299
def token(self, value):
300300
self._token = value
301301

302+
@property
303+
def account_id(self):
304+
# TODO: this needs to be resolved
305+
raise NotImplementedError(
306+
"missing call to self._refresh. "
307+
"Use get_frozen_credentials instead"
308+
)
309+
return self._account_id
310+
311+
@account_id.setter
312+
def account_id(self, value):
313+
self._account_id = value
314+
302315
async def _refresh(self):
303316
if not self.refresh_needed(self._advisory_refresh_timeout):
304317
return
@@ -347,7 +360,7 @@ async def _protected_refresh(self, is_mandatory):
347360
return
348361
self._set_from_data(metadata)
349362
self._frozen_credentials = ReadOnlyCredentials(
350-
self._access_key, self._secret_key, self._token
363+
self._access_key, self._secret_key, self._token, self._account_id
351364
)
352365
if self._is_expired():
353366
msg = (
@@ -370,6 +383,7 @@ def __init__(self, refresh_using, method, time_fetcher=_local_now):
370383
self._access_key = None
371384
self._secret_key = None
372385
self._token = None
386+
self._account_id = None
373387
self._expiry_time = None
374388
self._time_fetcher = time_fetcher
375389
self._refresh_lock = asyncio.Lock()
@@ -399,13 +413,16 @@ async def _get_cached_credentials(self):
399413

400414
creds = response['Credentials']
401415
expiration = _serialize_if_needed(creds['Expiration'], iso=True)
402-
return {
416+
credentials = {
403417
'access_key': creds['AccessKeyId'],
404418
'secret_key': creds['SecretAccessKey'],
405419
'token': creds['SessionToken'],
406420
'expiry_time': expiration,
421+
'account_id': creds.get('AccountId'),
407422
}
408423

424+
return credentials
425+
409426

410427
class AioBaseAssumeRoleCredentialFetcher(
411428
BaseAssumeRoleCredentialFetcher, AioCachedCredentialFetcher
@@ -421,7 +438,9 @@ async def _get_credentials(self):
421438
kwargs = self._assume_role_kwargs()
422439
client = await self._create_client()
423440
async with client as sts:
424-
return await sts.assume_role(**kwargs)
441+
response = await sts.assume_role(**kwargs)
442+
self._add_account_id_to_response(response)
443+
return response
425444

426445
async def _create_client(self):
427446
"""Create an STS client using the source credentials."""
@@ -465,7 +484,9 @@ async def _get_credentials(self):
465484
# the token, explicitly configure the client to not sign requests.
466485
config = AioConfig(signature_version=UNSIGNED)
467486
async with self._client_creator('sts', config=config) as client:
468-
return await client.assume_role_with_web_identity(**kwargs)
487+
response = await client.assume_role_with_web_identity(**kwargs)
488+
self._add_account_id_to_response(response)
489+
return response
469490

470491
def _assume_role_kwargs(self):
471492
"""Get the arguments for assume role based on current configuration."""
@@ -498,6 +519,7 @@ async def load(self):
498519
secret_key=creds_dict['secret_key'],
499520
token=creds_dict.get('token'),
500521
method=self.METHOD,
522+
account_id=creds_dict.get('account_id'),
501523
)
502524

503525
async def _retrieve_credentials_using(self, credential_process):
@@ -528,6 +550,7 @@ async def _retrieve_credentials_using(self, credential_process):
528550
'secret_key': parsed['SecretAccessKey'],
529551
'token': parsed.get('SessionToken'),
530552
'expiry_time': parsed.get('Expiration'),
553+
'account_id': self._get_account_id(parsed),
531554
}
532555
except KeyError as e:
533556
raise CredentialRetrievalError(
@@ -573,13 +596,15 @@ async def load(self):
573596
expiry_time,
574597
refresh_using=fetcher,
575598
method=self.METHOD,
599+
account_id=credentials['account_id'],
576600
)
577601

578602
return AioCredentials(
579603
credentials['access_key'],
580604
credentials['secret_key'],
581605
credentials['token'],
582606
method=self.METHOD,
607+
account_id=credentials['account_id'],
583608
)
584609
else:
585610
return None
@@ -621,8 +646,13 @@ async def load(self):
621646
config, self.ACCESS_KEY, self.SECRET_KEY
622647
)
623648
token = self._get_session_token(config)
649+
account_id = self._get_account_id(config)
624650
return AioCredentials(
625-
access_key, secret_key, token, method=self.METHOD
651+
access_key,
652+
secret_key,
653+
token,
654+
method=self.METHOD,
655+
account_id=account_id,
626656
)
627657

628658

@@ -643,8 +673,13 @@ async def load(self):
643673
profile_config, self.ACCESS_KEY, self.SECRET_KEY
644674
)
645675
token = self._get_session_token(profile_config)
676+
account_id = self._get_account_id(profile_config)
646677
return AioCredentials(
647-
access_key, secret_key, token, method=self.METHOD
678+
access_key,
679+
secret_key,
680+
token,
681+
method=self.METHOD,
682+
account_id=account_id,
648683
)
649684
else:
650685
return None
@@ -748,8 +783,8 @@ async def _resolve_credentials_from_profile(self, profile_name):
748783
):
749784
# This is only here for backwards compatibility. If this provider
750785
# isn't given a profile provider builder we still want to be able
751-
# handle the basic static credential case as we would before the
752-
# provile provider builder parameter was added.
786+
# to handle the basic static credential case as we would before the
787+
# profile provider builder parameter was added.
753788
return self._resolve_static_credentials_from_profile(profile)
754789
elif self._has_static_credentials(
755790
profile
@@ -920,6 +955,7 @@ async def _retrieve_or_fail(self):
920955
method=self.METHOD,
921956
expiry_time=_parse_if_needed(creds['expiry_time']),
922957
refresh_using=fetcher,
958+
account_id=creds.get('account_id'),
923959
)
924960

925961
def _create_fetcher(self, full_uri, *args, **kwargs):
@@ -941,6 +977,7 @@ async def fetch_creds():
941977
'secret_key': response['SecretAccessKey'],
942978
'token': response['Token'],
943979
'expiry_time': response['Expiration'],
980+
'account_id': response.get('AccountId'),
944981
}
945982

946983
return fetch_creds
@@ -1004,6 +1041,7 @@ async def _get_credentials(self):
10041041
'Expiration': self._parse_timestamp(
10051042
credentials['expiration']
10061043
),
1044+
'AccountId': self._account_id,
10071045
},
10081046
}
10091047
return credentials

aiobotocore/session.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from botocore.exceptions import PartialCredentialsError
55
from botocore.session import EVENT_ALIASES, ServiceModel
66
from botocore.session import Session as _SyncSession
7-
from botocore.session import UnknownServiceError, copy
7+
from botocore.session import UnknownServiceError, copy, logger
88

99
from . import __version__, retryhandler
1010
from .client import AioBaseClient, AioClientCreator
@@ -82,8 +82,12 @@ def _register_response_parser_factory(self):
8282
'response_parser_factory', AioResponseParserFactory()
8383
)
8484

85-
def set_credentials(self, access_key, secret_key, token=None):
86-
self._credentials = AioCredentials(access_key, secret_key, token)
85+
def set_credentials(
86+
self, access_key, secret_key, token=None, account_id=None
87+
):
88+
self._credentials = AioCredentials(
89+
access_key, secret_key, token, account_id=account_id
90+
)
8791

8892
async def get_credentials(self):
8993
if self._credentials is None:
@@ -130,6 +134,7 @@ async def _create_client(
130134
aws_secret_access_key=None,
131135
aws_session_token=None,
132136
config=None,
137+
aws_account_id=None,
133138
):
134139
default_client_config = self.get_default_client_config()
135140
# If a config is provided and a default config is set, then
@@ -165,6 +170,7 @@ async def _create_client(
165170
access_key=aws_access_key_id,
166171
secret_key=aws_secret_access_key,
167172
token=aws_session_token,
173+
account_id=aws_account_id,
168174
)
169175
elif self._missing_cred_vars(aws_access_key_id, aws_secret_access_key):
170176
raise PartialCredentialsError(
@@ -174,6 +180,13 @@ async def _create_client(
174180
),
175181
)
176182
else:
183+
if ignored_credentials := self._get_ignored_credentials(
184+
aws_session_token, aws_account_id
185+
):
186+
logger.debug(
187+
f"Ignoring the following credential-related values which were set without "
188+
f"an access key id and secret key on the session or client: {ignored_credentials}"
189+
)
177190
credentials = await self.get_credentials()
178191
auth_token = self.get_auth_token()
179192
endpoint_resolver = self._get_internal_component('endpoint_resolver')

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ dynamic = ["version", "readme"]
3232
dependencies = [
3333
"aiohttp >= 3.9.2, < 4.0.0",
3434
"aioitertools >= 0.5.1, < 1.0.0",
35-
"botocore >= 1.36.20, < 1.36.24", # NOTE: When updating, always keep `project.optional-dependencies` aligned
35+
"botocore >= 1.37.0, < 1.37.2", # NOTE: When updating, always keep `project.optional-dependencies` aligned
3636
"python-dateutil >= 2.1, < 3.0.0",
3737
"jmespath >= 0.7.1, < 2.0.0",
3838
"multidict >= 6.0.0, < 7.0.0",
@@ -41,10 +41,10 @@ dependencies = [
4141

4242
[project.optional-dependencies]
4343
awscli = [
44-
"awscli >= 1.37.20, < 1.37.24",
44+
"awscli >= 1.38.0, < 1.38.2",
4545
]
4646
boto3 = [
47-
"boto3 >= 1.36.20, < 1.36.24",
47+
"boto3 >= 1.37.0, < 1.37.2",
4848
]
4949

5050
[project.urls]

tests/boto_tests/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313

14+
import binascii
1415
import contextlib
1516
import os
1617
import random
@@ -27,6 +28,15 @@
2728
_LOADER = botocore.loaders.Loader()
2829

2930

31+
def random_chars(num_chars):
32+
"""Returns random hex characters.
33+
34+
Useful for creating resources with random names.
35+
36+
"""
37+
return binascii.hexlify(os.urandom(int(num_chars / 2))).decode('ascii')
38+
39+
3040
def create_session(**kwargs):
3141
# Create a Session object. By default,
3242
# the _LOADER object is used as the loader

0 commit comments

Comments
 (0)