Skip to content

Commit 09f2546

Browse files
committed
Address reviews and comments
1 parent 4eb1b76 commit 09f2546

6 files changed

Lines changed: 169 additions & 135 deletions

File tree

packages/smithy-aws-core/src/smithy_aws_core/config/custom_resolvers.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from smithy_core.config.resolver import ConfigResolver
66
from smithy_core.retries import RetryStrategyOptions, RetryStrategyType
77

8-
from smithy_aws_core.config.validators import validate_retry_mode
8+
from smithy_aws_core.config.validators import validate_max_attempts, validate_retry_mode
99

1010

1111
def resolve_retry_strategy(
@@ -20,10 +20,10 @@ def resolve_retry_strategy(
2020
2121
:param resolver: The config resolver to use for resolution
2222
23-
:returns: Tuple of (RetryStrategyOptions or None, source_name or None).
24-
Returns (None, None) if neither retry_mode nor max_attempts is set.
23+
:returns: Tuple of (RetryStrategyOptions, source_name) if both retry_mode and max_attempts
24+
are resolved. Returns (None, None) if either value is missing.
2525
26-
For mixed sources, the source string includes both component sources:
26+
For mixed sources, the source name includes both component sources:
2727
"retry_mode=environment, max_attempts=config_file"
2828
"""
2929
# Get retry_mode
@@ -32,21 +32,20 @@ def resolve_retry_strategy(
3232
# Get max_attempts
3333
max_attempts, attempts_source = resolver.get("max_attempts")
3434

35-
# If neither is set, return None
36-
if retry_mode is None and max_attempts is None:
37-
return (None, None)
35+
if retry_mode is None or max_attempts is None:
36+
return None, None
3837

39-
if retry_mode is not None:
40-
retry_mode = validate_retry_mode(retry_mode, mode_source)
41-
retry_mode = cast(RetryStrategyType, retry_mode)
38+
retry_mode = validate_retry_mode(retry_mode, mode_source)
39+
retry_mode = cast(RetryStrategyType, retry_mode)
40+
41+
max_attempts = validate_max_attempts(max_attempts, attempts_source)
4242

43-
# Construct options with defaults
4443
options = RetryStrategyOptions(
45-
retry_mode=retry_mode or "standard",
46-
max_attempts=int(max_attempts) if max_attempts else None,
44+
retry_mode=retry_mode,
45+
max_attempts=max_attempts,
4746
)
4847

4948
# Construct mixed source string showing where each component came from
50-
source = f"retry_mode={mode_source or 'unresolved'}, max_attempts={attempts_source or 'unresolved'}"
49+
source = f"retry_mode={mode_source}, max_attempts={attempts_source}"
5150

5251
return (options, source)

packages/smithy-aws-core/src/smithy_aws_core/config/validators.py

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,49 +23,45 @@ def __init__(self, key: str, value: Any, reason: str, source: str | None = None)
2323
super().__init__(msg)
2424

2525

26-
def validate_region(region_name: Any, source: str | None = None) -> str | None:
27-
"""Validate AWS region format.
26+
def validate_host(host_name: Any, source: str | None = None) -> str:
27+
"""Validate host name format.
2828
29-
Valid formats:
30-
- us-east-1, us-west-2, eu-west-1, etc.
31-
- Pattern: {partition}-{region}-{number}
32-
33-
:param region_name: The region value to validate
29+
:param host_name: The value to validate
3430
:param source: The config source that provided this value
3531
36-
:returns: The validated region string, or None if value is None
32+
:returns: The validated value
3733
38-
:raises ConfigValidationError: If the region format is invalid
34+
:raises ConfigValidationError: If the value format is invalid
3935
"""
40-
if not isinstance(region_name, str):
36+
if not isinstance(host_name, str):
4137
raise ConfigValidationError(
42-
"region",
43-
region_name,
44-
f"Region must be a string, got {type(region_name).__name__}",
38+
"host",
39+
host_name,
40+
f"Host must be a string, got {type(host_name).__name__}",
4541
source,
4642
)
4743

4844
pattern = r"^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)$"
4945

50-
if not re.match(pattern, region_name):
46+
if not re.match(pattern, host_name):
5147
raise ConfigValidationError(
52-
"region",
53-
region_name,
54-
"Region doesn't match the pattern (e.g., 'us-west-2', 'eu-central-1')",
48+
"host",
49+
host_name,
50+
"Host doesn't match the pattern.",
5551
source,
5652
)
57-
return region_name
53+
return host_name
5854

5955

60-
def validate_retry_mode(retry_mode: Any, source: str | None = None) -> str | None:
56+
def validate_retry_mode(retry_mode: Any, source: str | None = None) -> str:
6157
"""Validate retry mode.
6258
6359
Valid values: 'standard', 'simple'
6460
6561
:param retry_mode: The retry mode value to validate
6662
:param source: The source that provided this value
6763
68-
:returns: The validated retry mode string, or None if value is None
64+
:returns: The validated retry mode string
6965
7066
:raises: ConfigValidationError: If the retry mode is invalid
7167
"""
@@ -77,40 +73,59 @@ def validate_retry_mode(retry_mode: Any, source: str | None = None) -> str | Non
7773
source,
7874
)
7975

80-
valid_modes = set(get_args(RetryStrategyType))
76+
valid_modes = get_args(RetryStrategyType)
8177

8278
if retry_mode not in valid_modes:
8379
raise ConfigValidationError(
8480
"retry_mode",
8581
retry_mode,
86-
f"Retry mode must be one of {RetryStrategyType}, got {retry_mode}",
82+
f"Retry mode must be one of {valid_modes}, got {retry_mode}",
8783
source,
8884
)
8985

9086
return retry_mode
9187

9288

93-
def validate_retry_strategy(value: Any, source: str | None = None) -> Any:
89+
def validate_max_attempts(max_attempts: str | int, source: str | None = None) -> int:
90+
"""Validate and convert max_attempts to integer.
91+
92+
:param max_attempts: The max attempts value (string or int)
93+
:param source: The source that provided this value
94+
95+
:returns: The validated max_attempts as an integer
96+
97+
:raises ConfigValidationError: If the value cannot be converted to an integer
98+
"""
99+
try:
100+
return int(max_attempts)
101+
except (ValueError, TypeError):
102+
raise ConfigValidationError(
103+
"max_attempts",
104+
max_attempts,
105+
f"max_attempts must be a number, got {type(max_attempts).__name__}",
106+
source,
107+
)
108+
109+
110+
def validate_retry_strategy(
111+
value: Any, source: str | None = None
112+
) -> RetryStrategy | RetryStrategyOptions:
94113
"""Validate retry strategy configuration.
95114
96-
:param value: The retry strategy value to validate (None is allowed and returns None)
97-
:param source: The source that provided this value (for error messages)
115+
:param value: The retry strategy value to validate
116+
:param source: The source that provided this value
98117
99118
:returns: The validated retry strategy (RetryStrategy or RetryStrategyOptions)
100119
101120
:raises: ConfigValidationError: If the value is not a valid retry strategy type
102121
"""
103-
# Allow RetryStrategy instances
104-
if isinstance(value, RetryStrategy):
105-
return value
106122

107-
# Allow RetryStrategyOptions instances
108-
if isinstance(value, RetryStrategyOptions):
123+
if isinstance(value, RetryStrategy | RetryStrategyOptions):
109124
return value
110125

111126
raise ConfigValidationError(
112127
"retry_strategy",
113128
value,
114-
f"Retry strategy must be a RetryStrategy or RetryStrategyOptions got {type(value).__name__}",
129+
f"retry_strategy must be RetryStrategy or RetryStrategyOptions, got {type(value).__name__}",
115130
source,
116131
)
Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
"""Unit tests for custom resolver functions."""
5-
64
from typing import Any
75

86
from smithy_aws_core.config.custom_resolvers import resolve_retry_strategy
@@ -28,29 +26,7 @@ def get(self, key: str) -> Any | None:
2826
class TestResolveCustomResolverRetryStrategy:
2927
"""Test suite for complex configuration resolution"""
3028

31-
def test_resolves_when_only_retry_mode_set(self) -> None:
32-
source = StubSource("environment", {"retry_mode": "standard"})
33-
resolver = ConfigResolver(sources=[source])
34-
35-
result, source_name = resolve_retry_strategy(resolver)
36-
37-
assert isinstance(result, RetryStrategyOptions)
38-
assert result.retry_mode == "standard"
39-
assert result.max_attempts is None
40-
assert source_name == "retry_mode=environment, max_attempts=unresolved"
41-
42-
def test_resolves_when_only_max_attempts_set(self) -> None:
43-
source = StubSource("environment", {"max_attempts": "5"})
44-
resolver = ConfigResolver(sources=[source])
45-
46-
result, source_name = resolve_retry_strategy(resolver)
47-
48-
assert isinstance(result, RetryStrategyOptions)
49-
assert result.retry_mode == "standard"
50-
assert result.max_attempts == 5
51-
assert source_name == "retry_mode=unresolved, max_attempts=environment"
52-
53-
def test_resolves_from_both_values_when_set(self) -> None:
29+
def test_resolves_from_both_values(self) -> None:
5430
# When both retry mode and max attempts are set
5531
# It should use source names for both values
5632
source = StubSource(
@@ -65,15 +41,6 @@ def test_resolves_from_both_values_when_set(self) -> None:
6541
assert result.max_attempts == 3
6642
assert source_name == "retry_mode=environment, max_attempts=environment"
6743

68-
def test_returns_none_when_neither_value_set(self) -> None:
69-
source = StubSource("environment", {})
70-
resolver = ConfigResolver(sources=[source])
71-
72-
result, source_name = resolve_retry_strategy(resolver)
73-
# It should return (None, None) when values not set
74-
assert result is None
75-
assert source_name is None
76-
7744
def test_tracks_different_sources_for_each_component(self) -> None:
7845
source1 = StubSource("environment", {"retry_mode": "standard"})
7946
source2 = StubSource("config_file", {"max_attempts": "5"})
@@ -86,24 +53,41 @@ def test_tracks_different_sources_for_each_component(self) -> None:
8653
assert result.max_attempts == 5
8754
assert source_name == "retry_mode=environment, max_attempts=config_file"
8855

89-
def test_tracks_source_when_only_max_attempts_set(self) -> None:
90-
source1 = StubSource("environment", {})
91-
source2 = StubSource("config_file", {"max_attempts": "5"})
92-
resolver = ConfigResolver(sources=[source1, source2])
93-
94-
result, source_name = resolve_retry_strategy(resolver)
95-
96-
assert isinstance(result, RetryStrategyOptions)
97-
assert result.retry_mode == "standard" # Default
98-
assert result.max_attempts == 5
99-
assert source_name == "retry_mode=unresolved, max_attempts=config_file"
100-
10156
def test_converts_max_attempts_string_to_int(self) -> None:
102-
source = StubSource("environment", {"max_attempts": "10"})
57+
source = StubSource(
58+
"environment", {"max_attempts": "10", "retry_mode": "standard"}
59+
)
10360
resolver = ConfigResolver(sources=[source])
10461

10562
result, _ = resolve_retry_strategy(resolver)
10663

10764
assert isinstance(result, RetryStrategyOptions)
10865
assert result.max_attempts == 10
10966
assert isinstance(result.max_attempts, int)
67+
68+
def test_returns_none_when_only_retry_mode_set(self) -> None:
69+
source = StubSource("environment", {"retry_mode": "standard"})
70+
resolver = ConfigResolver(sources=[source])
71+
72+
result, source_name = resolve_retry_strategy(resolver)
73+
74+
assert result is None
75+
assert source_name is None
76+
77+
def test_returns_none_when_only_max_attempts_set(self) -> None:
78+
source = StubSource("environment", {"max_attempts": "5"})
79+
resolver = ConfigResolver(sources=[source])
80+
81+
result, source_name = resolve_retry_strategy(resolver)
82+
83+
assert result is None
84+
assert source_name is None
85+
86+
def test_returns_none_when_both_values_missing(self) -> None:
87+
source = StubSource("environment", {})
88+
resolver = ConfigResolver(sources=[source])
89+
90+
result, source_name = resolve_retry_strategy(resolver)
91+
92+
assert result is None
93+
assert source_name is None

packages/smithy-aws-core/tests/unit/config/test_validators.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
import pytest
66
from smithy_aws_core.config.validators import (
77
ConfigValidationError,
8-
validate_region,
8+
validate_host,
9+
validate_max_attempts,
910
validate_retry_mode,
1011
)
1112

1213

1314
class TestValidators:
1415
@pytest.mark.parametrize("region", ["us-east-1", "eu-west-1", "ap-south-1"])
1516
def test_validate_region_accepts_valid_values(self, region: str) -> None:
16-
assert validate_region(region) == region
17+
assert validate_host(region) == region
1718

1819
@pytest.mark.parametrize("invalid", ["-invalid", "-east", "12345", 1234])
1920
def test_validate_region_rejects_invalid_values(self, invalid: str) -> None:
2021
with pytest.raises(ConfigValidationError):
21-
validate_region(invalid)
22+
validate_host(invalid)
2223

2324
@pytest.mark.parametrize("mode", ["standard", "simple"])
2425
def test_validate_retry_mode_accepts_valid_values(self, mode: str) -> None:
@@ -30,3 +31,17 @@ def test_validate_retry_mode_rejects_invalid_values(
3031
) -> None:
3132
with pytest.raises(ConfigValidationError):
3233
validate_retry_mode(invalid_mode)
34+
35+
def test_validate_invalid_max_attempts_raises_error(self) -> None:
36+
with pytest.raises(
37+
ConfigValidationError, match="max_attempts must be a number"
38+
):
39+
validate_max_attempts("abcd")
40+
41+
def test_invalid_retry_mode_error_message(self) -> None:
42+
with pytest.raises(ConfigValidationError) as exc_info:
43+
validate_retry_mode("random_mode")
44+
assert (
45+
"Invalid value for 'retry_mode': 'random_mode'. Retry mode must be one "
46+
"of ('simple', 'standard'), got random_mode" in str(exc_info.value)
47+
)

0 commit comments

Comments
 (0)