Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit 0c35cb1

Browse files
authored
Add profiles (#3)
* Add profiles * Fix pipelines errors
1 parent d217154 commit 0c35cb1

File tree

6 files changed

+75
-61
lines changed

6 files changed

+75
-61
lines changed

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ message: If you use this software, please cite it using these metadata.
33
title: Get AWS Regions Package
44
abstract: A simple package for retrieving a list of AWS regions.
55
type: software
6-
version: 0.1.0
6+
version: 0.1.1
77
date-released: 2024-06-27
88
repository-code: https://github.com/AWSToolbox/get-aws-regions-package
99
keywords:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ for all of the tools in our [AWS Toolbox](https://github.com/AWSToolbox).
6767
- `exclude_list`: Optional list of regions to exclude.
6868
- `all_regions`: Boolean flag to include all regions (default: True).
6969
- `details`: Boolean flag to return detailed information about each region (default: False).
70+
- `profile_name`: String to specify the name of the profile to use.
7071
- Returns:
7172
- If `details=True`: Sorted list of dictionaries containing detailed region information.
7273
- If `details=False`: Sorted list of region names as strings.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
setup(
1414
name='wolfsoftware.get-aws-regions',
15-
version='0.1.0',
15+
version='0.1.1',
1616
author='Wolf Software',
1717
author_email='[email protected]',
1818
description='A simple package for retrieving a list of AWS regions.',

tests/conftest.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22
Configuration for pytest tests, including fixtures and mock data for testing the wolfsoftware.get-aws-regions package.
33
44
Fixtures:
5-
- boto3_client_mock: Mocks the boto3 client for AWS interactions.
5+
- boto3_session_mock: Mocks the boto3 session for AWS interactions.
6+
- boto3_session_mock_with_exception: Mocks the boto3 session raising an exception during describe_regions.
67
78
Mock Data:
89
- mock_regions: A list of dictionaries representing mock AWS regions.
910
- mock_description: A dictionary representing the mock description of a specific AWS region.
1011
"""
11-
from typing import Any, Dict, Generator, List, Union
12-
13-
from unittest.mock import AsyncMock, MagicMock, patch
1412

13+
from typing import Any, Dict, Generator, List
14+
from unittest.mock import MagicMock, patch
1515
import pytest
1616

17-
1817
# Mock data
1918
mock_regions: List[Dict[str, str]] = [
2019
{"RegionName": "us-east-1", "OptInStatus": "opt-in-not-required"},
@@ -30,14 +29,16 @@
3029

3130

3231
@pytest.fixture
33-
def boto3_client_mock(mock_exception: Union[Exception, None] = None) -> Generator[Union[MagicMock, AsyncMock], Any, None]:
32+
def boto3_session_mock() -> Generator[MagicMock, None, None]:
3433
"""
35-
Fixture to mock the boto3 client.
34+
Fixture to mock the boto3 session.
3635
3736
Yields:
38-
MagicMock: A mock of the boto3 client.
37+
MagicMock: A mock of the boto3 session.
3938
"""
40-
with patch("boto3.client") as mock_client:
39+
with patch("boto3.Session") as mock_session:
40+
mock_session_instance: Any = mock_session.return_value
41+
4142
regions_mock = MagicMock()
4243
ssm_mock = MagicMock()
4344

@@ -47,27 +48,25 @@ def mock_get_parameter(Name) -> Dict[str, Dict[str, str]]:
4748
raise ValueError(f"Unknown parameter name: {Name}")
4849

4950
ssm_mock.get_parameter.side_effect = mock_get_parameter
50-
mock_client.side_effect = lambda service_name, *args, **kwargs: ssm_mock if service_name == "ssm" else regions_mock
5151

52-
# Conditionally set side effect for describe_regions based on mock_exception
53-
if mock_exception:
54-
print("HERE")
55-
regions_mock.describe_regions.side_effect = mock_exception
56-
regions_mock.describe_regions.side_effect = mock_exception
52+
mock_session_instance.client.side_effect = lambda service_name, *args, **kwargs: ssm_mock if service_name == "ssm" else regions_mock
53+
5754
regions_mock.describe_regions.return_value = {"Regions": mock_regions}
5855

59-
yield mock_client
56+
yield mock_session
6057

6158

6259
@pytest.fixture
63-
def boto3_client_mock_with_exception() -> Generator[MagicMock, None, None]:
60+
def boto3_session_mock_with_exception() -> Generator[MagicMock, None, None]:
6461
"""
65-
Fixture to mock the boto3 client raising an exception during describe_regions.
62+
Fixture to mock the boto3 session raising an exception during describe_regions.
6663
6764
Yields:
68-
MagicMock: A mock of the boto3 client.
65+
MagicMock: A mock of the boto3 session.
6966
"""
70-
with patch("boto3.client") as mock_client:
67+
with patch("boto3.Session") as mock_session:
68+
mock_session_instance: Any = mock_session.return_value
69+
7170
regions_mock = MagicMock()
7271
ssm_mock = MagicMock()
7372

@@ -80,6 +79,6 @@ def mock_get_parameter(Name) -> Dict[str, Dict[str, str]]:
8079

8180
regions_mock.describe_regions.side_effect = Exception("Test Exception")
8281

83-
mock_client.side_effect = lambda service_name, *args, **kwargs: ssm_mock if service_name == "ssm" else regions_mock
82+
mock_session_instance.client.side_effect = lambda service_name, *args, **kwargs: ssm_mock if service_name == "ssm" else regions_mock
8483

85-
yield mock_client
84+
yield mock_session

tests/test_get_aws_regions.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,39 +37,37 @@ def test_version() -> None:
3737
assert version != 'unknown', f"Expected version, but got {version}" # nosec: B101
3838

3939

40-
def test_get_region_list_all_regions(boto3_client_mock) -> None:
40+
def test_get_region_list_all_regions(boto3_session_mock) -> None:
4141
"""
4242
Test fetching all regions with detailed information.
4343
4444
Arguments:
45-
boto3_client_mock (fixture): The mocked boto3 client.
45+
boto3_session_mock (fixture): The mocked boto3 session.
4646
"""
47-
regions_mock: Any = boto3_client_mock.return_value
47+
regions_mock: Any = boto3_session_mock.return_value.client.return_value
4848
regions_mock.describe_regions.return_value = {"Regions": mock_regions}
4949

5050
result: List[Dict[str, str | bool]] | List[str] = get_region_list(details=True)
5151
result.sort(key=lambda x: x["RegionName"]) # Sort the result for consistent ordering
52-
print("Filtered Regions with Details:", result)
5352

5453
expected_result: List[Dict[str, str]] = [
5554
{"RegionName": "us-east-1", "OptInStatus": "opt-in-not-required", "GeographicalLocation": "US East (N. Virginia)"},
5655
{"RegionName": "us-west-1", "OptInStatus": "opt-in-not-required", "GeographicalLocation": "US West (N. California)"},
5756
{"RegionName": "eu-west-1", "OptInStatus": "opted-in", "GeographicalLocation": "EU West (Ireland)"}
5857
]
5958
expected_result.sort(key=lambda x: x["RegionName"]) # Sort the expected result for consistent ordering
60-
print("Expected Result:", expected_result)
6159

6260
assert result == expected_result # nosec: B101
6361

6462

65-
def test_get_region_list_include_filter(boto3_client_mock) -> None:
63+
def test_get_region_list_include_filter(boto3_session_mock) -> None:
6664
"""
6765
Test fetching regions with an include filter.
6866
6967
Arguments:
70-
boto3_client_mock (fixture): The mocked boto3 client.
68+
boto3_session_mock (fixture): The mocked boto3 session.
7169
"""
72-
regions_mock: Any = boto3_client_mock.return_value
70+
regions_mock: Any = boto3_session_mock.return_value.client.return_value
7371

7472
regions_mock.describe_regions.return_value = {"Regions": mock_regions}
7573

@@ -85,14 +83,14 @@ def test_get_region_list_include_filter(boto3_client_mock) -> None:
8583
assert result == expected_result # nosec: B101
8684

8785

88-
def test_get_region_list_exclude_filter(boto3_client_mock) -> None:
86+
def test_get_region_list_exclude_filter(boto3_session_mock) -> None:
8987
"""
9088
Test fetching regions with an exclude filter.
9189
9290
Arguments:
93-
boto3_client_mock (fixture): The mocked boto3 client.
91+
boto3_session_mock (fixture): The mocked boto3 session.
9492
"""
95-
regions_mock: Any = boto3_client_mock.return_value
93+
regions_mock: Any = boto3_session_mock.return_value.client.return_value
9694

9795
regions_mock.describe_regions.return_value = {"Regions": mock_regions}
9896

@@ -108,14 +106,14 @@ def test_get_region_list_exclude_filter(boto3_client_mock) -> None:
108106
assert result == expected_result # nosec: B101
109107

110108

111-
def test_get_region_list_no_details(boto3_client_mock) -> None:
109+
def test_get_region_list_no_details(boto3_session_mock) -> None:
112110
"""
113111
Test fetching region names without details.
114112
115113
Arguments:
116-
boto3_client_mock (fixture): The mocked boto3 client.
114+
boto3_session_mock (fixture): The mocked boto3 session.
117115
"""
118-
regions_mock: Any = boto3_client_mock.return_value
116+
regions_mock: Any = boto3_session_mock.return_value.client.return_value
119117

120118
regions_mock.describe_regions.return_value = {"Regions": mock_regions}
121119

@@ -128,12 +126,12 @@ def test_get_region_list_no_details(boto3_client_mock) -> None:
128126
assert result == expected_result # nosec: B101
129127

130128

131-
def test_get_region_list_exceptions(boto3_client_mock_with_exception) -> None: # pylint: disable=unused-argument
129+
def test_get_region_list_exceptions(boto3_session_mock_with_exception) -> None: # pylint: disable=unused-argument
132130
"""
133131
Test exception handling when an error occurs in fetching regions.
134132
135133
Arguments:
136-
boto3_client_mock (fixture): The mocked boto3 client.
134+
boto3_session_mock (fixture): The mocked boto3 session.
137135
"""
138136
# Use pytest.raises to catch RegionListingError
139137
with pytest.raises(RegionListingError) as excinfo:

wolfsoftware/get_aws_regions/functions.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
"""
2-
This module provides functionalities for retrieving and processing AWS region information.
2+
This module provides functionalities for retrieving and processing AWS region information with support for multiple AWS profiles.
33
4-
It includes the following capabilities:
4+
Capabilities:
55
- Fetching a list of all AWS regions, with the option to include or exclude regions based on account opt-in status.
66
- Retrieving the geographical location description for a specific AWS region from the AWS Systems Manager (SSM) Parameter Store.
77
- Fetching geographical locations for multiple AWS regions using concurrent threading for improved performance.
88
- Applying include and exclude filters to the list of regions to customize the output.
99
- Generating a comprehensive list of AWS regions, with optional detailed information about each region.
10+
- Utilizing different AWS profiles for the retrieval of region information.
1011
1112
Functions:
12-
- get_region_list: Main function to retrieve a list of AWS regions, optionally filtering by include and exclude lists and returning
13-
detailed information if required.
13+
- get_region_list: Main function to retrieve a list of AWS regions, optionally filtering by include and exclude lists,
14+
and returning detailed information if required.
1415
15-
Private Functions:
16+
Private Functions:
1617
- _fetch_all_regions: Retrieves a list of all AWS regions.
1718
- _fetch_region_description: Fetches the geographical location for a specific AWS region.
1819
- _fetch_region_descriptions: Fetches geographical locations for multiple AWS regions using threading.
@@ -24,28 +25,34 @@
2425
Dependencies:
2526
- boto3: AWS SDK for Python to interact with various AWS services like EC2 and SSM.
2627
- concurrent.futures: Standard library module to enable asynchronous execution using threading.
28+
- botocore.exceptions: Exceptions for handling errors during boto3 operations.
2729
2830
Usage:
29-
This module is intended to be used as part of the wolfsoftware.get-aws-regions package. The main entry point is the `get_region_list` function,
30-
which provides flexibility in retrieving and customizing the list of AWS regions based on user-defined criteria.
31+
This module is intended to be used as part of the wolfsoftware.get-aws-regions package.
32+
The main entry point is the `get_region_list` function, which provides flexibility in retrieving
33+
and customizing the list of AWS regions based on user-defined criteria, including the ability
34+
to specify different AWS profiles.
3135
"""
36+
3237
from typing import Any, List, Dict, Optional, Union
3338

3439
from concurrent.futures._base import Future
3540
from concurrent.futures import ThreadPoolExecutor, as_completed
3641

3742
import boto3 # pylint: disable=import-error
43+
from botocore.exceptions import BotoCoreError, ClientError
3844

3945
from .exceptions import RegionListingError
4046

4147

42-
def _fetch_all_regions(all_regions: bool = True) -> List[Dict[str, Union[str, bool]]]:
48+
def _fetch_all_regions(all_regions: bool = True, profile_name: Optional[str] = None) -> List[Dict[str, Union[str, bool]]]:
4349
"""
4450
Retrieve a list of all AWS regions.
4551
4652
Arguments:
4753
all_regions (bool): If True, list all available regions, including those not opted into.
4854
If False, list only regions opted into by the account.
55+
profile_name (Optional[str]): The name of the AWS profile to use.
4956
5057
Returns:
5158
List[Dict[str, Union[str, bool]]]: A list of dictionaries containing information about each region.
@@ -54,8 +61,9 @@ def _fetch_all_regions(all_regions: bool = True) -> List[Dict[str, Union[str, bo
5461
RegionListingError: If there is an error in retrieving the regions.
5562
"""
5663
try:
57-
# Initialize a session using Amazon EC2
58-
ec2: Any = boto3.client('ec2')
64+
# Initialize a session using Amazon EC2 with the specified profile
65+
session = boto3.Session(profile_name=profile_name) if profile_name else boto3.Session()
66+
ec2: Any = session.client('ec2')
5967

6068
# Retrieve a list of all available regions or only opted-in regions based on the flag
6169
if all_regions:
@@ -71,18 +79,19 @@ def _fetch_all_regions(all_regions: bool = True) -> List[Dict[str, Union[str, bo
7179

7280
return regions
7381

74-
except boto3.exceptions.Boto3Error as e:
82+
except (BotoCoreError, ClientError) as e:
7583
raise RegionListingError(f"An error occurred while listing regions: {str(e)}") from e
7684
except Exception as e:
7785
raise RegionListingError(f"An unexpected error occurred: {str(e)}") from e
7886

7987

80-
def _fetch_region_description(region_name: str) -> Dict[str, str]:
88+
def _fetch_region_description(region_name: str, profile_name: Optional[str] = None) -> Dict[str, str]:
8189
"""
8290
Fetch the geographical location for a specific AWS region from SSM Parameter Store.
8391
8492
Arguments:
8593
region_name (str): The name of the region to fetch the geographical location for.
94+
profile_name (Optional[str]): The name of the AWS profile to use.
8695
8796
Returns:
8897
Dict[str, str]: A dictionary containing the region name and its geographical location.
@@ -91,27 +100,29 @@ def _fetch_region_description(region_name: str) -> Dict[str, str]:
91100
RegionListingError: If there is an error in retrieving the region geographical location.
92101
"""
93102
try:
94-
# Initialize a session using Amazon SSM
95-
ssm: Any = boto3.client('ssm')
103+
# Initialize a session using Amazon SSM with the specified profile
104+
session = boto3.Session(profile_name=profile_name) if profile_name else boto3.Session()
105+
ssm: Any = session.client('ssm')
96106

97107
# Retrieve the parameter for the region description
98108
parameter_name: str = f"/aws/service/global-infrastructure/regions/{region_name}/longName"
99109
response: Any = ssm.get_parameter(Name=parameter_name)
100110

101111
return {region_name: response['Parameter']['Value']}
102112

103-
except boto3.exceptions.Boto3Error as e:
113+
except (BotoCoreError, ClientError) as e:
104114
raise RegionListingError(f"An error occurred while retrieving geographical location for region {region_name}: {str(e)}") from e
105115
except Exception as e:
106116
raise RegionListingError(f"An unexpected error occurred: {str(e)}") from e
107117

108118

109-
def _fetch_region_descriptions(region_names: List[str]) -> Dict[str, str]:
119+
def _fetch_region_descriptions(region_names: List[str], profile_name: Optional[str] = None) -> Dict[str, str]:
110120
"""
111121
Fetch geographical locations for multiple AWS regions from SSM Parameter Store using threading for better performance.
112122
113123
Arguments:
114124
region_names (List[str]): A list of region names to fetch geographical locations for.
125+
profile_name (Optional[str]): The name of the AWS profile to use.
115126
116127
Returns:
117128
Dict[str, str]: A dictionary mapping region codes to their geographical locations.
@@ -122,7 +133,10 @@ def _fetch_region_descriptions(region_names: List[str]) -> Dict[str, str]:
122133
descriptions: Dict = {}
123134

124135
with ThreadPoolExecutor() as executor:
125-
future_to_region: Dict[Future[Dict[str, str]], str] = {executor.submit(_fetch_region_description, region): region for region in region_names}
136+
future_to_region: Dict[Future[Dict[str, str]], str] = {
137+
executor.submit(_fetch_region_description, region, profile_name): region
138+
for region in region_names
139+
}
126140

127141
for future in as_completed(future_to_region):
128142
region: str = future_to_region[future]
@@ -166,8 +180,9 @@ def _apply_region_filters(
166180
def get_region_list(
167181
include_list: Optional[List[str]] = None,
168182
exclude_list: Optional[List[str]] = None,
169-
all_regions: bool = True,
170-
details: bool = False
183+
all_regions: Optional[bool] = True,
184+
details: Optional[bool] = False,
185+
profile_name: Optional[str] = None
171186
) -> Union[List[Dict[str, Union[str, bool]]], List[str]]:
172187
"""
173188
Retrieve a list of AWS regions, optionally filtering by include and exclude lists.
@@ -179,6 +194,7 @@ def get_region_list(
179194
exclude_list (Optional[List[str]]): A list of regions to exclude. These regions will be omitted from the returned list if specified.
180195
all_regions (bool): If True, list all available regions, including those not opted into. If False, list only regions opted into by the account.
181196
details (bool): If True, return detailed information about each region. If False, return only the region names.
197+
profile_name (Optional[str]): The name of the AWS profile to use.
182198
183199
Returns:
184200
Union[List[Dict[str, Union[str, bool]]], List[str]]: A sorted list of regions with detailed information or just the region names.
@@ -187,15 +203,15 @@ def get_region_list(
187203
RegionListingError: If there is an error in retrieving the regions.
188204
"""
189205
try:
190-
all_regions_list: List[Dict[str, str | bool]] = _fetch_all_regions(all_regions)
206+
all_regions_list: List[Dict[str, str | bool]] = _fetch_all_regions(all_regions, profile_name)
191207
except Exception as e:
192208
raise RegionListingError(f"An error occurred while retrieving regions: {str(e)}") from e
193209

194210
filtered_regions: List[Dict[str, str | bool]] = _apply_region_filters(all_regions_list, include_list, exclude_list)
195211

196212
if details:
197213
region_names: List[str | bool] = [region['RegionName'] for region in filtered_regions]
198-
region_descriptions: Dict[str, str] = _fetch_region_descriptions(region_names)
214+
region_descriptions: Dict[str, str] = _fetch_region_descriptions(region_names, profile_name)
199215
for region in filtered_regions:
200216
region['GeographicalLocation'] = region_descriptions.get(region['RegionName'], "Unknown")
201217
print("Filtered Regions with Details:", filtered_regions) # Debug print

0 commit comments

Comments
 (0)