From 6c2c799b1e1a7144aa294fc8fc5735f7f8a69f43 Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:49:45 -0500 Subject: [PATCH 1/4] Add config source interface and environment variable source --- .../src/smithy_aws_core/config/__init__.py | 5 + .../src/smithy_aws_core/config/sources.py | 34 ++++++ .../tests/unit/config/test_sources.py | 100 ++++++++++++++++++ .../src/smithy_core/interfaces/source.py | 25 +++++ 4 files changed, 164 insertions(+) create mode 100644 packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py create mode 100644 packages/smithy-aws-core/src/smithy_aws_core/config/sources.py create mode 100644 packages/smithy-aws-core/tests/unit/config/test_sources.py create mode 100644 packages/smithy-core/src/smithy_core/interfaces/source.py diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py b/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py new file mode 100644 index 000000000..0f5cabddd --- /dev/null +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py @@ -0,0 +1,5 @@ +from .sources import EnvironmentSource + +__all__ = [ + "EnvironmentSource", +] diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py b/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py new file mode 100644 index 000000000..4f638d40e --- /dev/null +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py @@ -0,0 +1,34 @@ +import os +from typing import Any + + +class EnvironmentSource: + """Configuration from environment variables.""" + + SOURCE = "environment" + + def __init__(self, prefix: str = "AWS_"): + """Initialize the EnvironmentSource with environment variable prefix. + + :param prefix: Prefix for environment variables (default: 'AWS_') + """ + self._prefix = prefix + + @property + def name(self) -> str: + """Returns the source name.""" + return self.SOURCE + + def get(self, key: str) -> Any | None: + """Returns a configuration value from environment variables. + + :param key: The standard configuration key (e.g., 'region', 'retry_mode'). + + :returns: The value from the corresponding environment variable, or None if not set or empty. + """ + env_var = f"{self._prefix}{key.upper()}" + config_value = os.environ.get(env_var) + if config_value is None: + return None + stripped = config_value.strip() + return stripped if stripped else None diff --git a/packages/smithy-aws-core/tests/unit/config/test_sources.py b/packages/smithy-aws-core/tests/unit/config/test_sources.py new file mode 100644 index 000000000..d98f726a2 --- /dev/null +++ b/packages/smithy-aws-core/tests/unit/config/test_sources.py @@ -0,0 +1,100 @@ +import os +from unittest.mock import patch + +from smithy_aws_core.config.sources import EnvironmentSource +from smithy_core.interfaces.source import ConfigSource + + +class TestEnvironmentSource: + def test_implements_config_source_protocol(self): + source = EnvironmentSource() + assert isinstance(source, ConfigSource) + assert hasattr(source, "name") + assert hasattr(source, "get") + assert callable(source.get) + + def test_source_name(self): + source = EnvironmentSource() + assert source.name == "environment" + + def test_get_region_from_aws_region(self): + with patch.dict(os.environ, {"AWS_REGION": "us-west-2"}, clear=False): + source = EnvironmentSource() + value = source.get("region") + assert value == "us-west-2" + + def test_get_returns_none_when_env_var_not_set(self): + with patch.dict(os.environ, {}, clear=True): + source = EnvironmentSource() + value = source.get("region") + assert value is None + + def test_get_returns_none_for_unknown_key(self): + source = EnvironmentSource() + value = source.get("unknown_config_key") + assert value is None + + def test_get_handles_empty_string_env_var(self): + with patch.dict(os.environ, {"AWS_REGION": ""}, clear=False): + source = EnvironmentSource() + value = source.get("region") + # Empty string should be treated as None + assert value is None + + def test_get_handles_whitespace_env_var(self): + with patch.dict(os.environ, {"AWS_REGION": " us-west-2 "}, clear=False): + source = EnvironmentSource() + value = source.get("region") + # Whitespaces should be stripped + assert value == "us-west-2" + + def test_get_handles_whole_whitespace_env_var(self): + with patch.dict(os.environ, {"AWS_REGION": " "}, clear=False): + source = EnvironmentSource() + value = source.get("region") + # Whitespaces should be stripped + assert value is None + + def test_multiple_keys_with_different_env_vars(self): + env_vars = {"AWS_REGION": "eu-west-1", "AWS_RETRY_MODE": "standard"} + with patch.dict(os.environ, env_vars, clear=False): + source = EnvironmentSource() + + region = source.get("region") + retry_mode = source.get("retry_mode") + + assert region == "eu-west-1" + assert retry_mode == "standard" + + def test_get_is_idempotent(self): + with patch.dict(os.environ, {"AWS_REGION": "ap-south-1"}, clear=False): + source = EnvironmentSource() + # Calling get on source multiple times should return the same value + value1 = source.get("region") + value2 = source.get("region") + value3 = source.get("region") + + assert value1 == value2 == value3 == "ap-south-1" + + def test_source_does_not_cache_env_vars(self): + source = EnvironmentSource() + + # First read + with patch.dict(os.environ, {"AWS_REGION": "us-east-1"}, clear=False): + value1 = source.get("region") + assert value1 == "us-east-1" + + # Environment changes + with patch.dict(os.environ, {"AWS_REGION": "us-west-2"}, clear=False): + value2 = source.get("region") + assert value2 == "us-west-2" + + # Source reads from os.environ and not from cache + assert value1 != value2 + + def test_env_var_names_are_case_sensative(self): + with patch.dict(os.environ, {"aws_region": "us-west-2"}, clear=False): + source = EnvironmentSource() + value = source.get("region") + # Should not find 'aws_region' (lowercase), only 'AWS_REGION' + assert value is None diff --git a/packages/smithy-core/src/smithy_core/interfaces/source.py b/packages/smithy-core/src/smithy_core/interfaces/source.py new file mode 100644 index 000000000..1f33e6453 --- /dev/null +++ b/packages/smithy-core/src/smithy_core/interfaces/source.py @@ -0,0 +1,25 @@ +from typing import Any, Protocol, runtime_checkable + + +@runtime_checkable +class ConfigSource(Protocol): + """Protocol for configuration sources that provide values from various locations + like environment variables and configuration files. + """ + + @property + def name(self) -> str: + """Returns a string identifying the source. + + :returns: A string identifier for this source. + """ + ... + + def get(self, key: str) -> Any | None: + """Returns a configuration value from the source. + + :param key: The configuration key to retrieve (e.g., 'region') + + :returns: The value associated with the key, or None if not found. + """ + ... From 0b68f0adafdf446fbee6aa5b72053316dcdb0bcc Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:34:14 -0500 Subject: [PATCH 2/4] Remove unnecessary tests and add a valid license header on the files --- .../src/smithy_aws_core/config/__init__.py | 2 ++ .../src/smithy_aws_core/config/sources.py | 5 +++-- .../tests/unit/config/test_sources.py | 19 ++++--------------- .../src/smithy_core/interfaces/source.py | 2 ++ 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py b/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py index 0f5cabddd..e4ac5fd5f 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/__init__.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 from .sources import EnvironmentSource __all__ = [ diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py b/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py index 4f638d40e..f0728f6e6 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 import os from typing import Any @@ -30,5 +32,4 @@ def get(self, key: str) -> Any | None: config_value = os.environ.get(env_var) if config_value is None: return None - stripped = config_value.strip() - return stripped if stripped else None + return config_value.strip() diff --git a/packages/smithy-aws-core/tests/unit/config/test_sources.py b/packages/smithy-aws-core/tests/unit/config/test_sources.py index d98f726a2..6424e8698 100644 --- a/packages/smithy-aws-core/tests/unit/config/test_sources.py +++ b/packages/smithy-aws-core/tests/unit/config/test_sources.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 import os from unittest.mock import patch @@ -6,12 +8,6 @@ class TestEnvironmentSource: - def test_implements_config_source_protocol(self): - source = EnvironmentSource() - assert isinstance(source, ConfigSource) - assert hasattr(source, "name") - assert hasattr(source, "get") - assert callable(source.get) def test_source_name(self): source = EnvironmentSource() @@ -39,7 +35,7 @@ def test_get_handles_empty_string_env_var(self): source = EnvironmentSource() value = source.get("region") # Empty string should be treated as None - assert value is None + assert value is '' def test_get_handles_whitespace_env_var(self): with patch.dict(os.environ, {"AWS_REGION": " us-west-2 "}, clear=False): @@ -53,7 +49,7 @@ def test_get_handles_whole_whitespace_env_var(self): source = EnvironmentSource() value = source.get("region") # Whitespaces should be stripped - assert value is None + assert value is '' def test_multiple_keys_with_different_env_vars(self): env_vars = {"AWS_REGION": "eu-west-1", "AWS_RETRY_MODE": "standard"} @@ -91,10 +87,3 @@ def test_source_does_not_cache_env_vars(self): # Source reads from os.environ and not from cache assert value1 != value2 - - def test_env_var_names_are_case_sensative(self): - with patch.dict(os.environ, {"aws_region": "us-west-2"}, clear=False): - source = EnvironmentSource() - value = source.get("region") - # Should not find 'aws_region' (lowercase), only 'AWS_REGION' - assert value is None diff --git a/packages/smithy-core/src/smithy_core/interfaces/source.py b/packages/smithy-core/src/smithy_core/interfaces/source.py index 1f33e6453..add120a02 100644 --- a/packages/smithy-core/src/smithy_core/interfaces/source.py +++ b/packages/smithy-core/src/smithy_core/interfaces/source.py @@ -1,3 +1,5 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 from typing import Any, Protocol, runtime_checkable From 274a08d23c3c75bee698358ba321ab1211714644 Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:37:28 -0500 Subject: [PATCH 3/4] Update tests format --- packages/smithy-aws-core/tests/unit/config/test_sources.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/smithy-aws-core/tests/unit/config/test_sources.py b/packages/smithy-aws-core/tests/unit/config/test_sources.py index 6424e8698..1d74f3de2 100644 --- a/packages/smithy-aws-core/tests/unit/config/test_sources.py +++ b/packages/smithy-aws-core/tests/unit/config/test_sources.py @@ -4,11 +4,9 @@ from unittest.mock import patch from smithy_aws_core.config.sources import EnvironmentSource -from smithy_core.interfaces.source import ConfigSource class TestEnvironmentSource: - def test_source_name(self): source = EnvironmentSource() assert source.name == "environment" @@ -35,7 +33,7 @@ def test_get_handles_empty_string_env_var(self): source = EnvironmentSource() value = source.get("region") # Empty string should be treated as None - assert value is '' + assert value == "" def test_get_handles_whitespace_env_var(self): with patch.dict(os.environ, {"AWS_REGION": " us-west-2 "}, clear=False): @@ -49,7 +47,7 @@ def test_get_handles_whole_whitespace_env_var(self): source = EnvironmentSource() value = source.get("region") # Whitespaces should be stripped - assert value is '' + assert value == "" def test_multiple_keys_with_different_env_vars(self): env_vars = {"AWS_REGION": "eu-west-1", "AWS_RETRY_MODE": "standard"} From 608f5a2e6d52d01f8b2fd72838361d196fc0f7a3 Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:38:34 -0500 Subject: [PATCH 4/4] Address comments --- .../src/smithy_aws_core/config/sources.py | 12 ++++++------ .../tests/unit/config/test_sources.py | 15 +++++++-------- .../interfaces/{source.py => config.py} | 0 3 files changed, 13 insertions(+), 14 deletions(-) rename packages/smithy-core/src/smithy_core/interfaces/{source.py => config.py} (100%) diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py b/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py index f0728f6e6..f5e5195de 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/sources.py @@ -1,14 +1,11 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import os -from typing import Any class EnvironmentSource: """Configuration from environment variables.""" - SOURCE = "environment" - def __init__(self, prefix: str = "AWS_"): """Initialize the EnvironmentSource with environment variable prefix. @@ -19,14 +16,17 @@ def __init__(self, prefix: str = "AWS_"): @property def name(self) -> str: """Returns the source name.""" - return self.SOURCE + return "environment" - def get(self, key: str) -> Any | None: + def get(self, key: str) -> str | None: """Returns a configuration value from environment variables. + The key is transformed to uppercase and prefixed (e.g., 'region' → 'AWS_REGION'). + :param key: The standard configuration key (e.g., 'region', 'retry_mode'). - :returns: The value from the corresponding environment variable, or None if not set or empty. + :returns: The value from the environment variable (or empty string if set to empty), + or None if the variable is not set. """ env_var = f"{self._prefix}{key.upper()}" config_value = os.environ.get(env_var) diff --git a/packages/smithy-aws-core/tests/unit/config/test_sources.py b/packages/smithy-aws-core/tests/unit/config/test_sources.py index 1d74f3de2..5db84f38e 100644 --- a/packages/smithy-aws-core/tests/unit/config/test_sources.py +++ b/packages/smithy-aws-core/tests/unit/config/test_sources.py @@ -12,7 +12,7 @@ def test_source_name(self): assert source.name == "environment" def test_get_region_from_aws_region(self): - with patch.dict(os.environ, {"AWS_REGION": "us-west-2"}, clear=False): + with patch.dict(os.environ, {"AWS_REGION": "us-west-2"}, clear=True): source = EnvironmentSource() value = source.get("region") assert value == "us-west-2" @@ -29,21 +29,20 @@ def test_get_returns_none_for_unknown_key(self): assert value is None def test_get_handles_empty_string_env_var(self): - with patch.dict(os.environ, {"AWS_REGION": ""}, clear=False): + with patch.dict(os.environ, {"AWS_REGION": ""}, clear=True): source = EnvironmentSource() value = source.get("region") - # Empty string should be treated as None assert value == "" def test_get_handles_whitespace_env_var(self): - with patch.dict(os.environ, {"AWS_REGION": " us-west-2 "}, clear=False): + with patch.dict(os.environ, {"AWS_REGION": " us-west-2 "}, clear=True): source = EnvironmentSource() value = source.get("region") # Whitespaces should be stripped assert value == "us-west-2" def test_get_handles_whole_whitespace_env_var(self): - with patch.dict(os.environ, {"AWS_REGION": " "}, clear=False): + with patch.dict(os.environ, {"AWS_REGION": " "}, clear=True): source = EnvironmentSource() value = source.get("region") # Whitespaces should be stripped @@ -51,7 +50,7 @@ def test_get_handles_whole_whitespace_env_var(self): def test_multiple_keys_with_different_env_vars(self): env_vars = {"AWS_REGION": "eu-west-1", "AWS_RETRY_MODE": "standard"} - with patch.dict(os.environ, env_vars, clear=False): + with patch.dict(os.environ, env_vars, clear=True): source = EnvironmentSource() region = source.get("region") @@ -61,7 +60,7 @@ def test_multiple_keys_with_different_env_vars(self): assert retry_mode == "standard" def test_get_is_idempotent(self): - with patch.dict(os.environ, {"AWS_REGION": "ap-south-1"}, clear=False): + with patch.dict(os.environ, {"AWS_REGION": "ap-south-1"}, clear=True): source = EnvironmentSource() # Calling get on source multiple times should return the same value value1 = source.get("region") @@ -74,7 +73,7 @@ def test_source_does_not_cache_env_vars(self): source = EnvironmentSource() # First read - with patch.dict(os.environ, {"AWS_REGION": "us-east-1"}, clear=False): + with patch.dict(os.environ, {"AWS_REGION": "us-east-1"}, clear=True): value1 = source.get("region") assert value1 == "us-east-1" diff --git a/packages/smithy-core/src/smithy_core/interfaces/source.py b/packages/smithy-core/src/smithy_core/interfaces/config.py similarity index 100% rename from packages/smithy-core/src/smithy_core/interfaces/source.py rename to packages/smithy-core/src/smithy_core/interfaces/config.py