Skip to content

Reference

This section provides a structured breakdown of the main application module and its supporting utilities used in the eraXplor project.

🌟eraXplor source code


πŸ”ΉMain Application Module

▢️ Entry Point

eraXplor - AWS Cost Export Tool

This is the main entry point for the eraXplor CLI tool, which allows users to export AWS cost and usage data using AWS Cost Explorer.

It provides an interactive command-line workflow to: 1. Prompt the user for a date range (start and end dates). 2. Prompt for an AWS CLI profile to authenticate with. 3. Allow the user to select a cost grouping dimension (e.g., by account, service, Purchase type, Usage type.) 4. Fetch cost data using the AWS Cost Explorer API. 5. Export the resulting data to a CSV file.

Examples:

>>> eraXplor
Enter a start date value with YYYY-MM-DD format: 2025-1-1
Enter a end date value with YYYY-MM-DD format: 2025-3-30
Enter your AWS Profile name:  [Profile name]
Enter the cost group by key:
    Enter [1] to list by 'LINKED_ACCOUNT' -> Default
    Enter [2] to list by 'SERVICE'
    Enter [3] to list by 'PURCHASE_TYPE'
    Enter [4] to list by 'USAGE_TYPE'
    Press Enter for 'LINKED_ACCOUNT' -> Default:

βœ… Data exported to test_output.csv

main()

Orchestrates & Manage depends of cost export workflow.

Source code in eraXplor/__main__.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def main() -> None:
    """Orchestrates & Manage depends of cost export workflow."""
    # Banner
    banner_format, copyright_notice = generate_banner()
    print(f"\n\n {termcolor.colored(banner_format, color="green")}")
    print(f"{termcolor.colored(copyright_notice, color="green")}", end="\n\n")

    # Prompt user for input
    start_date_input = get_start_date_from_user()
    if start_date_input is None:
        print("Exiting due to invalid date input.")
        return  # immediately exits the main() function

    end_date_input = get_end_date_from_user()

    # Prompt for AWS profile name
    aws_profile_name_input = input("Enter your AWS Profile name: ")

    # Prompt for cost group by key
    cost_groupby_key_input = get_cost_groupby_key()

    # Fetch monthly account cost usage
    fetch_monthly_account_cost_usage = monthly_account_cost_export(
        start_date_input, end_date_input,
        aws_profile_name_input,
        cost_groupby_key_input)

    # Export results to CSV
    csv_export(fetch_monthly_account_cost_usage)

This is the primary script responsible for orchestrating the user workflow. It handles user input, invokes AWS cost data retrieval, and manages data export functionality.


πŸ›  Utility Modules

Module to display a banner and copyright notice.

banner()

Generates a banner and copyright notice for the application.

Source code in eraXplor/utils/banner_utils.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def banner():
    """Generates a banner and copyright notice for the application."""

    copyright_notice = """╔══════════════════════════════════════════════════╗
β•‘  Β© 2025 Mohamed eraki                            β•‘
β•‘  mohamed-ibrahim2021@outlook.com                 β•‘
β•‘  Version: 1.0.0                                  β•‘
β•‘  eraXplor - AWS Cost exporter Tool               β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
    """
    banner_format = pyfiglet.figlet_format("eraXplor", font='slant')
    return banner_format, copyright_notice

Responsible for rendering styled ASCII banners and displaying copyright information used in the CLI interface.


πŸ“Š Cost Export Utilities

Module to retrieve AWS account cost data using AWS Cost Explorer API.

CostRecord

Bases: TypedDict

Class type annotation tool dettermining the List Schema. Type definition for a single cost record.

Source code in eraXplor/utils/cost_export_utils.py
42
43
44
45
46
47
48
49
class CostRecord(TypedDict):
    """Class type annotation tool dettermining the List Schema.
    Type definition for a single cost record.
    """

    time_period: Dict[str, str]  # {'Start': str, 'End': str}
    account_id: str
    account_cost: str

get_cost_groupby_key()

Iteratively prompts the user to select a cost group by key.

Source code in eraXplor/utils/cost_export_utils.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def get_cost_groupby_key():
    """Iteratively prompts the user to select a cost group by key."""
    while True:
        try:
            # Prompt user for input
            cost_groupby_key_input = input(
                """Enter the cost group by key:
    Enter [1] to list by 'LINKED_ACCOUNT' -> Default
    Enter [2] to list by 'SERVICE'
    Enter [3] to list by 'PURCHASE_TYPE'
    Enter [4] to list by 'USAGE_TYPE'
    Press Enter for 'LINKED_ACCOUNT' -> Default:\n"""
            ).strip()

            # use default if empty
            if cost_groupby_key_input == "":
                cost_groupby_key_object = "1"
                print("Defaulting to 'LINKED_ACCOUNT'")
            else:
                cost_groupby_key_object = cost_groupby_key_input

            # Ensure input is valid
            if cost_groupby_key_object not in ["1", "2", "3", "4"]:
                print("Invalid selection. Please enter [1], [2], [3] or [4].")
                continue
            # Return the valid selection
            return int(cost_groupby_key_object)

        except KeyboardInterrupt:
            print("\nUser interrupted. Exiting")
            break

monthly_account_cost_export(start_date_input, end_date_input, aws_profile_name_input, cost_groupby_key_input=1)

Retrieves AWS account cost data for a specified time period using AWS Cost Explorer.

Fetches the unblended costs for all linked accounts in an AWS organization for a given date range, grouped by account ID and returned in monthly granularity.

Parameters:

Name Type Description Default
start_date_input str

The start date of the cost report in YYYY-MM-DD format.

required
end_date_input str

The end date of the cost report in YYYY-MM-DD format.

required
aws_profile_name_input str

The name of the AWS profile to use for authentication, as configured in the local AWS credentials file.

required

Returns:

Name Type Description
list List[CostRecord]

A list of dictionaries containing cost data, where each dictionary has: - time_period (dict): Contains 'Start' and 'End' dates for the time period - account_id (str): The AWS account ID - account_cost (str): The unblended cost amount as a string

Raises:

Type Description
ClientError

If there are AWS API authorization or parameter issues

ProfileNotFound

If the specified AWS profile doesn't exist

Source code in eraXplor/utils/cost_export_utils.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def monthly_account_cost_export(
    start_date_input: Union[str, datetime],  # str | datetime
    end_date_input: Union[str, datetime],
    aws_profile_name_input: str,
    cost_groupby_key_input: int = 1,
) -> List[CostRecord]:
    """Retrieves AWS account cost data for a specified time period using AWS Cost Explorer.

    Fetches the unblended costs for all linked accounts in an AWS organization for a given
    date range, grouped by account ID and returned in monthly granularity.

    Args:
        start_date_input (str): The start date of the cost report in YYYY-MM-DD format.
        end_date_input (str): The end date of the cost report in YYYY-MM-DD format.
        aws_profile_name_input (str): The name of the AWS profile to use for authentication,
            as configured in the local AWS credentials file.

    Returns:
        list: A list of dictionaries containing cost data, where each dictionary has:
            - time_period (dict): Contains 'Start' and 'End' dates for the time period
            - account_id (str): The AWS account ID
            - account_cost (str): The unblended cost amount as a string

    Raises:
        botocore.exceptions.ClientError: If there are AWS API authorization or parameter issues
        botocore.exceptions.ProfileNotFound: If the specified AWS profile doesn't exist
    """
    profile_session = boto3.Session(profile_name=str(aws_profile_name_input))
    ce_client = profile_session.client("ce")

    # if condition determine the type of groupby key
    results = []
    if cost_groupby_key_input == 1:
        # group by account ID
        account_cost_usage = ce_client.get_cost_and_usage(
            TimePeriod={"Start": str(start_date_input), "End": str(end_date_input)},
            Granularity="MONTHLY",
            Metrics=["UnblendedCost"],
            GroupBy=[  # group the result based on account ID
                {"Type": "DIMENSION", "Key": "LINKED_ACCOUNT"}
            ],
        )
        for item in account_cost_usage["ResultsByTime"]:
            time_period = item["TimePeriod"]
            for group in item["Groups"]:
                account_id = group["Keys"][0]
                account_cost = group["Metrics"]["UnblendedCost"]["Amount"]
                results.append(
                    {
                        "time_period": time_period,
                        "account_id": account_id,
                        "account_cost": account_cost,
                    }
                )
    elif cost_groupby_key_input == 2:
        account_cost_usage = ce_client.get_cost_and_usage(
            TimePeriod={"Start": str(start_date_input), "End": str(end_date_input)},
            Granularity="MONTHLY",
            Metrics=["UnblendedCost"],
            GroupBy=[  # group the result based on service
                {"Type": "DIMENSION", "Key": "SERVICE"}
            ],
        )
        for item in account_cost_usage["ResultsByTime"]:
            time_period = item["TimePeriod"]
            for group in item["Groups"]:
                service_name = group["Keys"][0]
                service_cost = group["Metrics"]["UnblendedCost"]["Amount"]
                results.append(
                    {
                        "time_period": time_period,
                        "service_name": service_name,
                        "service_cost": service_cost,
                    }
                )
    elif cost_groupby_key_input == 3:
        account_cost_usage = ce_client.get_cost_and_usage(
            TimePeriod={"Start": str(start_date_input), "End": str(end_date_input)},
            Granularity="MONTHLY",
            Metrics=["UnblendedCost"],
            GroupBy=[{"Type": "DIMENSION", "Key": "PURCHASE_TYPE"}],
        )
        for item in account_cost_usage["ResultsByTime"]:
            time_period = item["TimePeriod"]
            for group in item["Groups"]:
                service_name = group["Keys"][0]
                service_cost = group["Metrics"]["UnblendedCost"]["Amount"]
                results.append(
                    {
                        "time_period": time_period,
                        "service_name": service_name,
                        "service_cost": service_cost,
                    }
                )
    elif cost_groupby_key_input == 4:
        account_cost_usage = ce_client.get_cost_and_usage(
            TimePeriod={"Start": str(start_date_input), "End": str(end_date_input)},
            Granularity="MONTHLY",
            Metrics=["UnblendedCost"],
            GroupBy=[{"Type": "DIMENSION", "Key": "USAGE_TYPE"}],
        )
        for item in account_cost_usage["ResultsByTime"]:
            time_period = item["TimePeriod"]
            for group in item["Groups"]:
                service_name = group["Keys"][0]
                service_cost = group["Metrics"]["UnblendedCost"]["Amount"]
                results.append(
                    {
                        "time_period": time_period,
                        "service_name": service_name,
                        "service_cost": service_cost,
                    }
                )
    return results

Contains functions for retrieving cost and usage reports from AWS Cost Explorer using boto3, grouped by various dimensions such as:

  • Linked AWS accounts
  • AWS services
  • Purchase types
  • Usage types

🧾 CSV Export Utilities

Module for exporting AWS cost data to CSV format.

csv_export(results, filename='cost_repot.csv')

Exports AWS cost data to a CSV file with standardized formatting.

Takes the output from monthly_account_cost_export() (i.e. depends handle by main) and writes it to a CSV file with consistent column headers and proper formatting. The CSV will contain the time period, Account/Service/Purchase_type/Usage_type, and associated costs.

Parameters:

Name Type Description Default
fetch_monthly_account_cost_usage list

List of cost data dictionaries as returned by monthly_account_cost_export(). Each dictionary should contain: - time_period (dict): With 'Start' and 'End' keys - [] (str): AWS account ID | service name - account_cost (str): Cost amount as string

required
filename str

Output filename for the CSV. Defaults to 'cost_report.csv'.

'cost_repot.csv'

Returns:

Name Type Description
None None

Writes directly to file but doesn't return any value.

Source code in eraXplor/utils/csv_export_utils.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def csv_export(
    results: List[Dict[str, Any]],
    filename: str = "cost_repot.csv"
    ) -> None:
    """Exports AWS cost data to a CSV file with standardized formatting.

    Takes the output from monthly_account_cost_export() _(i.e. depends handle by main)_
    and writes it to a CSV file with consistent column headers and proper formatting.
    The CSV will contain the time period, Account/Service/Purchase_type/Usage_type,
    and associated costs.

    Args:
        fetch_monthly_account_cost_usage (list): List of cost data dictionaries as returned
            by monthly_account_cost_export(). Each dictionary should contain:
            - time_period (dict): With 'Start' and 'End' keys
            - [<account_id | service name>] (str): AWS account ID | service name
            - account_cost (str): Cost amount as string
        filename (str, optional): Output filename for the CSV. Defaults to 'cost_report.csv'.

    Returns:
        None: Writes directly to file but doesn't return any value.
    """
    # Create a CSV file with write mode
    with open(filename, mode="w", newline="", encoding="utf-8") as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(
            [
                "Start Date",
                "End Date",
                "Account/Service/Purchase_type/Usage_type",
                "Cost",
            ]
        )
        for row in results:
            time_period = row["time_period"]
            name = row.get("account_id") or row.get("service_name")
            cost = row.get("account_cost") or row.get("service_cost")
            writer.writerow([time_period["Start"], time_period["End"], name, cost])
    print(f"\nβœ… Data exported to {filename}")

Provides functionality to export retrieved cost data into a structured CSV format.


πŸ“… Date Utilities

Module providing date utility functions.

get_end_date_from_user()

Prompts the user to enter an end date and validates the input format.

Continuously prompts the user up to 4 times until a valid date is provided in the specified format or until the user interrupts with keyboard input. Handles both format validation and user interruption gracefully.

Returns:

Type Description

datetime.date, 'Too many invalid attempts', or None: Returns a date object if valid input is provided, returns None if the user interrupts the input (Ctrl+C).

Raises:

Type Description
ValueError

If the input date format is invalid.

KeyboardInterrupt

If the user interrupts the input prompt (though this is caught and handled within the function).

Source code in eraXplor/utils/date_utils.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def get_end_date_from_user():
    """Prompts the user to enter an end date and validates the input format.

    Continuously prompts the user up to 4 times until a valid date is provided in the specified
    format or until the user interrupts with keyboard input. Handles both format
    validation and user interruption gracefully.

    Returns:
        datetime.date, 'Too many invalid attempts', or None: Returns a date object if valid input is provided,
            returns None if the user interrupts the input (Ctrl+C).

    Raises:
        ValueError: If the input date format is invalid.

        KeyboardInterrupt: If the user interrupts the input prompt (though this is
            caught and handled within the function).
    """
    attempts = 0
    while attempts < 4:
        try:
            date_string = input("Enter an end date value with YYYY-MM-DD format: ")
            date_object = datetime.strptime(date_string, "%Y-%m-%d").date()
            return date_object
        except ValueError:
            print("Invalid date format, Please use YYYY-MM-DD")
        except KeyboardInterrupt:
            print("\nUser interrupted. Exiting")
            break
        attempts += 1
    print("Too many invalid attempts. Exiting.")
    return None

get_start_date_from_user()

Prompts the user to enter a start date and validates the input format.

Continuously prompts the user up to 4 times until a valid date is provided in the specified format or until the user interrupts with keyboard input. Handles both format validation and user interruption gracefully.

Returns:

Type Description

datetime.date, 'Too many invalid attempts', or None: Returns a date object if valid input is provided, returns None if the user interrupts the input (Ctrl+C).

Raises:

Type Description
ValueError

If the input date format is invalid.

KeyboardInterrupt

If the user interrupts the input prompt (though this is caught and handled within the function).

Source code in eraXplor/utils/date_utils.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def get_start_date_from_user():
    """Prompts the user to enter a start date and validates the input format.

    Continuously prompts the user up to 4 times until a valid date is provided in the specified
    format or until the user interrupts with keyboard input. Handles both format
    validation and user interruption gracefully.

    Returns:
        datetime.date, 'Too many invalid attempts', or None: Returns a date object if valid input is provided,
            returns None if the user interrupts the input (Ctrl+C).

    Raises:
        ValueError: If the input date format is invalid.

        KeyboardInterrupt: If the user interrupts the input prompt (though this is
            caught and handled within the function).
    """
    attempts = 0
    while attempts < 4:
        try:
            date_string = input("Enter a start date value with YYYY-MM-DD format: ")
            date_object = datetime.strptime(date_string, "%Y-%m-%d").date()
            return date_object
        except ValueError:
            print("Invalid date format, Please use YYYY-MM-DD")
        except KeyboardInterrupt:
            print("\nUser interrupted. Exiting")
            break
        attempts +=1
    print("Too many invalid attempts. Exiting.")
    return None

Includes interactive functions for prompting and validating date input from users, ensuring format compliance and error handling.