Skip to content

Commit a73f8c2

Browse files
committed
bump botocore dependency specification
1 parent 38973fe commit a73f8c2

7 files changed

Lines changed: 267 additions & 4 deletions

File tree

aiobotocore/hooks.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
parse_get_bucket_location as boto_parse_get_bucket_location,
99
)
1010
from botocore.hooks import HierarchicalEmitter, logger
11+
from botocore.signers import (
12+
add_dsql_generate_db_auth_token_methods as boto_add_dsql_generate_db_auth_token_methods,
13+
)
1114
from botocore.signers import (
1215
add_generate_db_auth_token as boto_add_generate_db_auth_token,
1316
)
@@ -25,6 +28,7 @@
2528
parse_get_bucket_location,
2629
)
2730
from .signers import (
31+
add_dsql_generate_db_auth_token_methods,
2832
add_generate_db_auth_token,
2933
add_generate_presigned_post,
3034
add_generate_presigned_url,
@@ -37,6 +41,7 @@
3741
boto_add_generate_presigned_post: add_generate_presigned_post,
3842
boto_add_generate_db_auth_token: add_generate_db_auth_token,
3943
boto_parse_get_bucket_location: parse_get_bucket_location,
44+
boto_add_dsql_generate_db_auth_token_methods: add_dsql_generate_db_auth_token_methods,
4045
}
4146

4247

aiobotocore/signers.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import botocore
44
import botocore.auth
5-
from botocore.exceptions import UnknownClientMethodError
5+
from botocore.exceptions import ParamValidationError, UnknownClientMethodError
66
from botocore.signers import (
77
RequestSigner,
88
S3PostPresigner,
@@ -200,6 +200,15 @@ def add_generate_db_auth_token(class_attributes, **kwargs):
200200
class_attributes['generate_db_auth_token'] = generate_db_auth_token
201201

202202

203+
def add_dsql_generate_db_auth_token_methods(class_attributes, **kwargs):
204+
class_attributes['generate_db_connect_auth_token'] = (
205+
dsql_generate_db_connect_auth_token
206+
)
207+
class_attributes['generate_db_connect_admin_auth_token'] = (
208+
dsql_generate_db_connect_admin_auth_token
209+
)
210+
211+
203212
async def generate_db_auth_token(
204213
self, DBHostname, Port, DBUsername, Region=None
205214
):
@@ -256,6 +265,86 @@ async def generate_db_auth_token(
256265
return presigned_url[len(scheme) :]
257266

258267

268+
async def _dsql_generate_db_auth_token(
269+
self, Hostname, Action, Region=None, ExpiresIn=900
270+
):
271+
"""Generate a DSQL database token for an arbitrary action.
272+
:type Hostname: str
273+
:param Hostname: The DSQL endpoint host name.
274+
:type Action: str
275+
:param Action: Action to perform on the cluster (DbConnectAdmin or DbConnect).
276+
:type Region: str
277+
:param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.
278+
:type ExpiresIn: int
279+
:param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).
280+
:return: A presigned url which can be used as an auth token.
281+
"""
282+
possible_actions = ("DbConnect", "DbConnectAdmin")
283+
284+
if Action not in possible_actions:
285+
raise ParamValidationError(
286+
report=f"Received {Action} for action but expected one of: {', '.join(possible_actions)}"
287+
)
288+
289+
if Region is None:
290+
Region = self.meta.region_name
291+
292+
request_dict = {
293+
'url_path': '/',
294+
'query_string': '',
295+
'headers': {},
296+
'body': {
297+
'Action': Action,
298+
},
299+
'method': 'GET',
300+
}
301+
scheme = 'https://'
302+
endpoint_url = f'{scheme}{Hostname}'
303+
prepare_request_dict(request_dict, endpoint_url)
304+
presigned_url = await self._request_signer.generate_presigned_url(
305+
operation_name=Action,
306+
request_dict=request_dict,
307+
region_name=Region,
308+
expires_in=ExpiresIn,
309+
signing_name='dsql',
310+
)
311+
return presigned_url[len(scheme) :]
312+
313+
314+
async def dsql_generate_db_connect_auth_token(
315+
self, Hostname, Region=None, ExpiresIn=900
316+
):
317+
"""Generate a DSQL database token for the "DbConnect" action.
318+
:type Hostname: str
319+
:param Hostname: The DSQL endpoint host name.
320+
:type Region: str
321+
:param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.
322+
:type ExpiresIn: int
323+
:param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).
324+
:return: A presigned url which can be used as an auth token.
325+
"""
326+
return await _dsql_generate_db_auth_token(
327+
self, Hostname, "DbConnect", Region, ExpiresIn
328+
)
329+
330+
331+
async def dsql_generate_db_connect_admin_auth_token(
332+
self, Hostname, Region=None, ExpiresIn=900
333+
):
334+
"""Generate a DSQL database token for the "DbConnectAdmin" action.
335+
:type Hostname: str
336+
:param Hostname: The DSQL endpoint host name.
337+
:type Region: str
338+
:param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used.
339+
:type ExpiresIn: int
340+
:param ExpiresIn: The token expiry duration in seconds (default is 900 seconds).
341+
:return: A presigned url which can be used as an auth token.
342+
"""
343+
return await _dsql_generate_db_auth_token(
344+
self, Hostname, "DbConnectAdmin", Region, ExpiresIn
345+
)
346+
347+
259348
class AioS3PostPresigner(S3PostPresigner):
260349
async def generate_presigned_post(
261350
self,

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ classifiers = [
3030
dynamic = ["version", "readme"]
3131

3232
dependencies = [
33-
"botocore >=1.35.67, <1.35.74", # NOTE: When updating, always keep `project.optional-dependencies` aligned
33+
"botocore >=1.35.74, <1.35.82", # NOTE: When updating, always keep `project.optional-dependencies` aligned
3434
"aiohttp >=3.9.2, <4.0.0",
3535
"wrapt >=1.10.10, <2.0.0",
3636
"aioitertools >=0.5.1, <1.0.0",
3737
]
3838

3939
[project.optional-dependencies]
4040
awscli = [
41-
"awscli >=1.36.8, <1.36.15",
41+
"awscli >=1.36.15, <1.36.23",
4242
]
4343
boto3 = [
44-
"boto3 >=1.35.67, <1.35.74",
44+
"boto3 >=1.35.74, <1.35.82",
4545
]
4646

4747
[project.urls]

requirements-dev.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ docker~=7.1
2222
moto[server,s3,sqs,awslambda,dynamodb,cloudformation,sns,batch,ec2,rds]~=4.2.9
2323
pre-commit~=3.5.0
2424
pytest-asyncio~=0.23.8
25+
time-machine~=2.15.0
2526
tomli; python_version < "3.11" # Requirement for tests/test_version.py

tests/boto_tests/__init__.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
14+
from botocore.compat import parse_qs, urlparse
15+
16+
17+
def _urlparse(url):
18+
if isinstance(url, bytes):
19+
# Not really necessary, but it helps to reduce noise on Python 2.x
20+
url = url.decode('utf8')
21+
return urlparse(url)
22+
23+
24+
def assert_url_equal(url1, url2):
25+
parts1 = _urlparse(url1)
26+
parts2 = _urlparse(url2)
27+
28+
# Because the query string ordering isn't relevant, we have to parse
29+
# every single part manually and then handle the query string.
30+
assert parts1.scheme == parts2.scheme
31+
assert parts1.netloc == parts2.netloc
32+
assert parts1.path == parts2.path
33+
assert parts1.params == parts2.params
34+
assert parts1.fragment == parts2.fragment
35+
assert parts1.username == parts2.username
36+
assert parts1.password == parts2.password
37+
assert parts1.hostname == parts2.hostname
38+
assert parts1.port == parts2.port
39+
assert parse_qs(parts1.query) == parse_qs(parts2.query)

tests/boto_tests/unit/test_signers.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
1+
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
113
import datetime
214
from datetime import timezone
315
from unittest import mock
416

17+
import pytest
18+
from botocore.exceptions import ParamValidationError
19+
520
import aiobotocore.credentials
621
import aiobotocore.session
722
import aiobotocore.signers
23+
from tests.boto_tests import assert_url_equal
24+
25+
DATE = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=timezone.utc)
826

927

1028
async def test_signers_generate_db_auth_token(rds_client):
@@ -31,3 +49,98 @@ async def test_signers_generate_db_auth_token(rds_client):
3149
assert result2.startswith(
3250
'prod-instance.us-east-1.rds.amazonaws.com:3306/?AWSAccessKeyId=xxx&'
3351
)
52+
53+
54+
class TestDSQLGenerateDBAuthToken:
55+
@pytest.fixture(scope="session")
56+
def hostname(self):
57+
return 'test.dsql.us-east-1.on.aws'
58+
59+
@pytest.fixture(scope="session")
60+
def action(self):
61+
return 'DbConnect'
62+
63+
@pytest.fixture
64+
async def client(self, session):
65+
async with session.create_client(
66+
'dsql',
67+
region_name='us-east-1',
68+
aws_access_key_id='ACCESS_KEY',
69+
aws_secret_access_key='SECRET_KEY',
70+
aws_session_token="SESSION_TOKEN",
71+
) as client:
72+
yield client
73+
74+
async def test_dsql_generate_db_auth_token(
75+
self, client, hostname, action, time_machine
76+
):
77+
time_machine.move_to(DATE, tick=False)
78+
79+
result = await aiobotocore.signers._dsql_generate_db_auth_token(
80+
client, hostname, action
81+
)
82+
83+
expected_result = (
84+
'test.dsql.us-east-1.on.aws/?Action=DbConnect'
85+
'&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='
86+
'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request'
87+
'&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host'
88+
'&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature='
89+
'57fe03e060348aaa21405c239bf02572bbc911076e94dcd65c12ae569dd8fcf4'
90+
)
91+
92+
# A scheme needs to be appended to the beginning or urlsplit may fail
93+
# on certain systems.
94+
assert_url_equal('https://' + result, 'https://' + expected_result)
95+
96+
async def test_dsql_generate_db_connect_auth_token(
97+
self, client, hostname, time_machine
98+
):
99+
time_machine.move_to(DATE, tick=False)
100+
101+
result = await aiobotocore.signers.dsql_generate_db_connect_auth_token(
102+
client, hostname
103+
)
104+
105+
expected_result = (
106+
'test.dsql.us-east-1.on.aws/?Action=DbConnect'
107+
'&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='
108+
'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request'
109+
'&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host'
110+
'&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature='
111+
'57fe03e060348aaa21405c239bf02572bbc911076e94dcd65c12ae569dd8fcf4'
112+
)
113+
114+
# A scheme needs to be appended to the beginning or urlsplit may fail
115+
# on certain systems.
116+
assert_url_equal('https://' + result, 'https://' + expected_result)
117+
118+
async def test_dsql_generate_db_connect_admin_auth_token(
119+
self, client, hostname, time_machine
120+
):
121+
time_machine.move_to(DATE, tick=False)
122+
123+
result = await aiobotocore.signers.dsql_generate_db_connect_admin_auth_token(
124+
client, hostname
125+
)
126+
127+
expected_result = (
128+
'test.dsql.us-east-1.on.aws/?Action=DbConnectAdmin'
129+
'&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='
130+
'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request'
131+
'&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host'
132+
'&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature='
133+
'5ac084bc7cabccc19a52a5d1b5c24b50d3ce143f43b659bd484c91aaf555e190'
134+
)
135+
136+
# A scheme needs to be appended to the beginning or urlsplit may fail
137+
# on certain systems.
138+
assert_url_equal('https://' + result, 'https://' + expected_result)
139+
140+
async def test_dsql_generate_db_auth_token_invalid_action(
141+
self, client, hostname
142+
):
143+
with pytest.raises(ParamValidationError):
144+
await aiobotocore.signers._dsql_generate_db_auth_token(
145+
client, hostname, "FooBar"
146+
)

tests/test_patches.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,13 @@
7777
from botocore.signers import (
7878
RequestSigner,
7979
S3PostPresigner,
80+
_dsql_generate_db_auth_token,
81+
add_dsql_generate_db_auth_token_methods,
8082
add_generate_db_auth_token,
8183
add_generate_presigned_post,
8284
add_generate_presigned_url,
85+
dsql_generate_db_connect_admin_auth_token,
86+
dsql_generate_db_connect_auth_token,
8387
generate_db_auth_token,
8488
generate_presigned_post,
8589
generate_presigned_url,
@@ -497,6 +501,18 @@
497501
},
498502
add_generate_db_auth_token: {'f61014e6fac4b5c7ee7ac2d2bec15fb16fa9fbe5'},
499503
generate_db_auth_token: {'1f37e1e5982d8528841ce6b79f229b3e23a18959'},
504+
add_dsql_generate_db_auth_token_methods: {
505+
'95c68a1aac8ee549e11b5dc010b6bb03f9ea00ea',
506+
},
507+
_dsql_generate_db_auth_token: {
508+
'53034b0475122209509db59fbd79a4ead70836cf',
509+
},
510+
dsql_generate_db_connect_auth_token: {
511+
'29b5919b695113c55452f2325d0ff66dd719a647'
512+
},
513+
dsql_generate_db_connect_admin_auth_token: {
514+
'd7e7a4899b8fd3a544dd1df95196517e2cfd5c84'
515+
},
500516
# tokens.py
501517
create_token_resolver: {'b287f4879235a4292592a49b201d2b0bc2dbf401'},
502518
DeferredRefreshableToken.__init__: {

0 commit comments

Comments
 (0)