runbooks 1.1.4__py3-none-any.whl → 1.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +138 -35
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +11 -0
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +63 -74
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +201 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/cloud_foundations_integration.py +144 -149
  113. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  114. runbooks/inventory/collectors/aws_networking.py +109 -99
  115. runbooks/inventory/collectors/base.py +4 -0
  116. runbooks/inventory/core/collector.py +495 -313
  117. runbooks/inventory/drift_detection_cli.py +69 -96
  118. runbooks/inventory/inventory_mcp_cli.py +48 -46
  119. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  120. runbooks/inventory/mcp_inventory_validator.py +549 -465
  121. runbooks/inventory/mcp_vpc_validator.py +359 -442
  122. runbooks/inventory/organizations_discovery.py +55 -51
  123. runbooks/inventory/rich_inventory_display.py +33 -32
  124. runbooks/inventory/unified_validation_engine.py +278 -251
  125. runbooks/inventory/vpc_analyzer.py +732 -695
  126. runbooks/inventory/vpc_architecture_validator.py +293 -348
  127. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  128. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  129. runbooks/main.py +49 -34
  130. runbooks/main_final.py +91 -60
  131. runbooks/main_minimal.py +22 -10
  132. runbooks/main_optimized.py +131 -100
  133. runbooks/main_ultra_minimal.py +7 -2
  134. runbooks/mcp/__init__.py +36 -0
  135. runbooks/mcp/integration.py +679 -0
  136. runbooks/monitoring/performance_monitor.py +9 -4
  137. runbooks/operate/dynamodb_operations.py +3 -1
  138. runbooks/operate/ec2_operations.py +145 -137
  139. runbooks/operate/iam_operations.py +146 -152
  140. runbooks/operate/networking_cost_heatmap.py +29 -8
  141. runbooks/operate/rds_operations.py +223 -254
  142. runbooks/operate/s3_operations.py +107 -118
  143. runbooks/operate/vpc_operations.py +646 -616
  144. runbooks/remediation/base.py +1 -1
  145. runbooks/remediation/commons.py +10 -7
  146. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  147. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  148. runbooks/remediation/multi_account.py +24 -21
  149. runbooks/remediation/rds_snapshot_list.py +86 -60
  150. runbooks/remediation/remediation_cli.py +92 -146
  151. runbooks/remediation/universal_account_discovery.py +83 -79
  152. runbooks/remediation/workspaces_list.py +46 -41
  153. runbooks/security/__init__.py +19 -0
  154. runbooks/security/assessment_runner.py +1150 -0
  155. runbooks/security/baseline_checker.py +812 -0
  156. runbooks/security/cloudops_automation_security_validator.py +509 -535
  157. runbooks/security/compliance_automation_engine.py +17 -17
  158. runbooks/security/config/__init__.py +2 -2
  159. runbooks/security/config/compliance_config.py +50 -50
  160. runbooks/security/config_template_generator.py +63 -76
  161. runbooks/security/enterprise_security_framework.py +1 -1
  162. runbooks/security/executive_security_dashboard.py +519 -508
  163. runbooks/security/multi_account_security_controls.py +959 -1210
  164. runbooks/security/real_time_security_monitor.py +422 -444
  165. runbooks/security/security_baseline_tester.py +1 -1
  166. runbooks/security/security_cli.py +143 -112
  167. runbooks/security/test_2way_validation.py +439 -0
  168. runbooks/security/two_way_validation_framework.py +852 -0
  169. runbooks/sre/production_monitoring_framework.py +167 -177
  170. runbooks/tdd/__init__.py +15 -0
  171. runbooks/tdd/cli.py +1071 -0
  172. runbooks/utils/__init__.py +14 -17
  173. runbooks/utils/logger.py +7 -2
  174. runbooks/utils/version_validator.py +50 -47
  175. runbooks/validation/__init__.py +6 -6
  176. runbooks/validation/cli.py +9 -3
  177. runbooks/validation/comprehensive_2way_validator.py +745 -704
  178. runbooks/validation/mcp_validator.py +906 -228
  179. runbooks/validation/terraform_citations_validator.py +104 -115
  180. runbooks/validation/terraform_drift_detector.py +447 -451
  181. runbooks/vpc/README.md +617 -0
  182. runbooks/vpc/__init__.py +8 -1
  183. runbooks/vpc/analyzer.py +577 -0
  184. runbooks/vpc/cleanup_wrapper.py +476 -413
  185. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  186. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  187. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  188. runbooks/vpc/config.py +92 -97
  189. runbooks/vpc/cost_engine.py +411 -148
  190. runbooks/vpc/cost_explorer_integration.py +553 -0
  191. runbooks/vpc/cross_account_session.py +101 -106
  192. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  193. runbooks/vpc/eni_gate_validator.py +961 -0
  194. runbooks/vpc/heatmap_engine.py +185 -160
  195. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  196. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  197. runbooks/vpc/networking_wrapper.py +15 -8
  198. runbooks/vpc/pdca_remediation_planner.py +528 -0
  199. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  200. runbooks/vpc/runbooks_adapter.py +1167 -241
  201. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  202. runbooks/vpc/test_data_loader.py +358 -0
  203. runbooks/vpc/tests/conftest.py +314 -4
  204. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  205. runbooks/vpc/tests/test_cost_engine.py +0 -2
  206. runbooks/vpc/topology_generator.py +326 -0
  207. runbooks/vpc/unified_scenarios.py +1297 -1124
  208. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  209. runbooks-1.1.5.dist-info/METADATA +328 -0
  210. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
  211. runbooks/finops/README.md +0 -414
  212. runbooks/finops/accuracy_cross_validator.py +0 -647
  213. runbooks/finops/business_cases.py +0 -950
  214. runbooks/finops/dashboard_router.py +0 -922
  215. runbooks/finops/ebs_optimizer.py +0 -973
  216. runbooks/finops/embedded_mcp_validator.py +0 -1629
  217. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  218. runbooks/finops/finops_dashboard.py +0 -584
  219. runbooks/finops/finops_scenarios.py +0 -1218
  220. runbooks/finops/legacy_migration.py +0 -730
  221. runbooks/finops/multi_dashboard.py +0 -1519
  222. runbooks/finops/single_dashboard.py +0 -1113
  223. runbooks/finops/unlimited_scenarios.py +0 -393
  224. runbooks-1.1.4.dist-info/METADATA +0 -800
  225. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  226. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  227. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  228. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -37,6 +37,7 @@ logger = logging.getLogger(__name__)
37
37
  @dataclass
38
38
  class AWSPricingResult:
39
39
  """Result of AWS pricing calculation."""
40
+
40
41
  service_key: str
41
42
  region: str
42
43
  monthly_cost: float
@@ -48,9 +49,9 @@ class AWSPricingResult:
48
49
  class DynamicAWSPricing:
49
50
  """
50
51
  Enterprise AWS Pricing Service - Universal Compatibility & Real-time Integration
51
-
52
+
52
53
  Strategic Features:
53
- - Universal AWS region/partition compatibility
54
+ - Universal AWS region/partition compatibility
54
55
  - Enterprise performance: <1s response time with intelligent caching
55
56
  - Real-time AWS Pricing API integration with thread-safe operations
56
57
  - Complete profile integration with --profile and --all patterns
@@ -62,7 +63,7 @@ class DynamicAWSPricing:
62
63
  def __init__(self, cache_ttl_hours: int = 24, enable_fallback: bool = True, profile: Optional[str] = None):
63
64
  """
64
65
  Initialize enterprise dynamic pricing engine.
65
-
66
+
66
67
  Args:
67
68
  cache_ttl_hours: Cache time-to-live in hours
68
69
  enable_fallback: Enable fallback to estimated pricing
@@ -74,28 +75,28 @@ class DynamicAWSPricing:
74
75
  self._pricing_cache = {}
75
76
  self._cache_lock = threading.RLock()
76
77
  self._executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="pricing")
77
-
78
+
78
79
  # Regional pricing cache - populated dynamically from AWS Pricing API
79
80
  # NO hardcoded multipliers - all pricing retrieved in real-time
80
81
  self._regional_pricing_cache = {}
81
82
  self._region_cache_lock = threading.RLock()
82
-
83
+
83
84
  console.print("[dim]Enterprise AWS Pricing Engine initialized with universal compatibility[/]")
84
85
  logger.info(f"Dynamic AWS Pricing Engine initialized with profile: {profile or 'default'}")
85
86
 
86
87
  def get_ec2_instance_pricing(self, instance_type: str, region: str = "us-east-1") -> AWSPricingResult:
87
88
  """
88
89
  Get dynamic pricing for EC2 instance type.
89
-
90
+
90
91
  Args:
91
92
  instance_type: EC2 instance type (e.g., t3.micro)
92
93
  region: AWS region for pricing lookup
93
-
94
+
94
95
  Returns:
95
96
  AWSPricingResult with current EC2 pricing information
96
97
  """
97
98
  cache_key = f"ec2_instance:{instance_type}:{region}"
98
-
99
+
99
100
  with self._cache_lock:
100
101
  # Check cache first
101
102
  if cache_key in self._pricing_cache:
@@ -106,20 +107,20 @@ class DynamicAWSPricing:
106
107
  else:
107
108
  # Cache expired, remove it
108
109
  del self._pricing_cache[cache_key]
109
-
110
+
110
111
  # Try to get real pricing from AWS API
111
112
  try:
112
113
  pricing_result = self._get_ec2_api_pricing(instance_type, region)
113
-
114
+
114
115
  # Cache the result
115
116
  with self._cache_lock:
116
117
  self._pricing_cache[cache_key] = pricing_result
117
-
118
+
118
119
  return pricing_result
119
-
120
+
120
121
  except Exception as e:
121
122
  logger.error(f"Failed to get AWS API pricing for {instance_type}: {e}")
122
-
123
+
123
124
  if self.enable_fallback:
124
125
  return self._get_ec2_fallback_pricing(instance_type, region)
125
126
  else:
@@ -131,40 +132,40 @@ class DynamicAWSPricing:
131
132
  def _get_ec2_api_pricing(self, instance_type: str, region: str) -> AWSPricingResult:
132
133
  """
133
134
  Get EC2 instance pricing from AWS Pricing API.
134
-
135
+
135
136
  Args:
136
137
  instance_type: EC2 instance type
137
138
  region: AWS region
138
-
139
+
139
140
  Returns:
140
141
  AWSPricingResult with real AWS pricing
141
142
  """
142
143
  import json
143
-
144
+
144
145
  try:
145
146
  # AWS Pricing API is only available in us-east-1 region
146
147
  # Use enhanced session management for universal AWS environment support
147
148
  if self.profile:
148
149
  # Use profile-aware session creation with proper credential resolution
149
- session = create_cost_session(self.profile)
150
- pricing_client = session.client('pricing', region_name='us-east-1')
150
+ session = create_cost_session(profile_name=self.profile)
151
+ pricing_client = session.client("pricing", region_name="us-east-1")
151
152
  logger.debug(f"Created EC2 pricing client with profile: {self.profile}")
152
153
  else:
153
154
  # Try environment-based credentials with fallback chain
154
155
  try:
155
156
  # First attempt: Use default credential chain
156
- pricing_client = boto3.client('pricing', region_name='us-east-1')
157
+ pricing_client = boto3.client("pricing", region_name="us-east-1")
157
158
  logger.debug("Created EC2 pricing client with default credentials")
158
159
  except NoCredentialsError:
159
160
  # Second attempt: Try with AWS_PROFILE if set
160
- aws_profile = os.getenv('AWS_PROFILE')
161
+ aws_profile = os.getenv("AWS_PROFILE")
161
162
  if aws_profile:
162
163
  session = boto3.Session(profile_name=aws_profile)
163
- pricing_client = session.client('pricing', region_name='us-east-1')
164
+ pricing_client = session.client("pricing", region_name="us-east-1")
164
165
  logger.debug(f"Created EC2 pricing client with AWS_PROFILE: {aws_profile}")
165
166
  else:
166
167
  raise NoCredentialsError("No AWS credentials available for Pricing API")
167
-
168
+
168
169
  # Query AWS Pricing API for EC2 instances - get multiple results to find on-demand pricing
169
170
  response = pricing_client.get_products(
170
171
  ServiceCode="AmazonEC2",
@@ -175,35 +176,35 @@ class DynamicAWSPricing:
175
176
  {"Type": "TERM_MATCH", "Field": "tenancy", "Value": "Shared"},
176
177
  {"Type": "TERM_MATCH", "Field": "operatingSystem", "Value": "Linux"},
177
178
  {"Type": "TERM_MATCH", "Field": "preInstalledSw", "Value": "NA"},
178
- {"Type": "TERM_MATCH", "Field": "licenseModel", "Value": "No License required"}
179
+ {"Type": "TERM_MATCH", "Field": "licenseModel", "Value": "No License required"},
179
180
  ],
180
- MaxResults=10 # Get more results to find on-demand pricing
181
+ MaxResults=10, # Get more results to find on-demand pricing
181
182
  )
182
-
183
- if not response.get('PriceList'):
183
+
184
+ if not response.get("PriceList"):
184
185
  raise ValueError(f"No pricing data found for {instance_type} in {region}")
185
-
186
+
186
187
  # Extract pricing from response - prioritize on-demand over reservation pricing
187
188
  hourly_rate = None
188
189
 
189
- for price_item in response['PriceList']:
190
+ for price_item in response["PriceList"]:
190
191
  try:
191
192
  price_data = json.loads(price_item)
192
- product = price_data.get('product', {})
193
- attributes = product.get('attributes', {})
193
+ product = price_data.get("product", {})
194
+ attributes = product.get("attributes", {})
194
195
 
195
196
  # Skip reservation instances, focus on on-demand
196
- usage_type = attributes.get('usagetype', '')
197
- market_option = attributes.get('marketoption', '')
197
+ usage_type = attributes.get("usagetype", "")
198
+ market_option = attributes.get("marketoption", "")
198
199
 
199
200
  # Skip if this is reservation pricing
200
- if 'reservation' in usage_type.lower() or 'reserved' in market_option.lower():
201
+ if "reservation" in usage_type.lower() or "reserved" in market_option.lower():
201
202
  logger.debug(f"Skipping reservation pricing for {instance_type}")
202
203
  continue
203
204
 
204
205
  # Navigate the pricing structure
205
- terms = price_data.get('terms', {})
206
- on_demand = terms.get('OnDemand', {})
206
+ terms = price_data.get("terms", {})
207
+ on_demand = terms.get("OnDemand", {})
207
208
 
208
209
  if not on_demand:
209
210
  continue
@@ -212,7 +213,7 @@ class DynamicAWSPricing:
212
213
  term_key = list(on_demand.keys())[0]
213
214
  term_data = on_demand[term_key]
214
215
 
215
- price_dimensions = term_data.get('priceDimensions', {})
216
+ price_dimensions = term_data.get("priceDimensions", {})
216
217
  if not price_dimensions:
217
218
  continue
218
219
 
@@ -220,10 +221,10 @@ class DynamicAWSPricing:
220
221
  price_dim_key = list(price_dimensions.keys())[0]
221
222
  price_dim = price_dimensions[price_dim_key]
222
223
 
223
- price_per_unit = price_dim.get('pricePerUnit', {})
224
- usd_price = price_per_unit.get('USD')
224
+ price_per_unit = price_dim.get("pricePerUnit", {})
225
+ usd_price = price_per_unit.get("USD")
225
226
 
226
- if usd_price and usd_price != '0.0000000000':
227
+ if usd_price and usd_price != "0.0000000000":
227
228
  hourly_rate = float(usd_price)
228
229
  logger.info(f"Found AWS API on-demand pricing for {instance_type}: ${hourly_rate}/hour")
229
230
  # Log the pricing source for debugging
@@ -233,24 +234,24 @@ class DynamicAWSPricing:
233
234
  except (KeyError, ValueError, IndexError, json.JSONDecodeError) as parse_error:
234
235
  logger.debug(f"Failed to parse EC2 pricing data: {parse_error}")
235
236
  continue
236
-
237
+
237
238
  if hourly_rate is None:
238
239
  raise ValueError(f"Could not extract valid pricing for {instance_type}")
239
-
240
+
240
241
  # Convert hourly to monthly (24 hours * 30 days)
241
242
  monthly_cost = hourly_rate * 24 * 30
242
-
243
+
243
244
  logger.info(f"AWS API pricing for {instance_type} in {region}: ${monthly_cost:.4f}/month")
244
-
245
+
245
246
  return AWSPricingResult(
246
247
  service_key=f"ec2_instance:{instance_type}",
247
248
  region=region,
248
249
  monthly_cost=monthly_cost,
249
250
  pricing_source="aws_api",
250
251
  last_updated=datetime.now(),
251
- currency="USD"
252
+ currency="USD",
252
253
  )
253
-
254
+
254
255
  except (ClientError, NoCredentialsError) as e:
255
256
  logger.warning(f"AWS Pricing API unavailable for {instance_type}: {e}")
256
257
  raise e
@@ -261,118 +262,122 @@ class DynamicAWSPricing:
261
262
  # ============================================================================
262
263
  # ENTERPRISE SERVICE PRICING METHODS - Strategic Requirements Implementation
263
264
  # ============================================================================
264
-
265
+
265
266
  def get_ec2_instance_hourly_cost(self, instance_type: str, region: str = "us-east-1") -> float:
266
267
  """
267
268
  Get EC2 instance hourly cost (Strategic Requirement #1).
268
-
269
+
269
270
  Args:
270
271
  instance_type: EC2 instance type (e.g., t3.micro)
271
272
  region: AWS region for pricing lookup
272
-
273
+
273
274
  Returns:
274
275
  Hourly cost in USD
275
276
  """
276
277
  result = self.get_ec2_instance_pricing(instance_type, region)
277
278
  return result.monthly_cost / (24 * 30) # Convert monthly to hourly
278
-
279
+
279
280
  def get_eip_monthly_cost(self, region: str = "us-east-1") -> float:
280
281
  """
281
282
  Get Elastic IP monthly cost (Strategic Requirement #2).
282
-
283
+
283
284
  Args:
284
285
  region: AWS region for pricing lookup
285
-
286
+
286
287
  Returns:
287
288
  Monthly cost in USD for unassociated EIP
288
289
  """
289
290
  result = self.get_service_pricing("elastic_ip", region)
290
291
  return result.monthly_cost
291
-
292
+
292
293
  def get_nat_gateway_monthly_cost(self, region: str = "us-east-1") -> float:
293
294
  """
294
295
  Get NAT Gateway monthly cost (Strategic Requirement #3).
295
-
296
+
296
297
  Args:
297
298
  region: AWS region for pricing lookup
298
-
299
+
299
300
  Returns:
300
301
  Monthly cost in USD for NAT Gateway
301
302
  """
302
303
  result = self.get_service_pricing("nat_gateway", region)
303
304
  return result.monthly_cost
304
-
305
+
305
306
  def get_ebs_gb_monthly_cost(self, volume_type: str = "gp3", region: str = "us-east-1") -> float:
306
307
  """
307
308
  Get EBS per-GB monthly cost (Strategic Requirement #4).
308
-
309
+
309
310
  Args:
310
311
  volume_type: EBS volume type (gp3, gp2, io1, io2, st1, sc1)
311
312
  region: AWS region for pricing lookup
312
-
313
+
313
314
  Returns:
314
315
  Monthly cost per GB in USD
315
316
  """
316
317
  result = self.get_service_pricing(f"ebs_{volume_type}", region)
317
318
  return result.monthly_cost
318
-
319
+
319
320
  # Additional Enterprise Service Methods
320
321
  def get_vpc_endpoint_monthly_cost(self, region: str = "us-east-1") -> float:
321
322
  """Get VPC Endpoint monthly cost."""
322
323
  result = self.get_service_pricing("vpc_endpoint", region)
323
324
  return result.monthly_cost
324
-
325
+
325
326
  def get_transit_gateway_monthly_cost(self, region: str = "us-east-1") -> float:
326
327
  """Get Transit Gateway monthly cost."""
327
328
  result = self.get_service_pricing("transit_gateway", region)
328
329
  return result.monthly_cost
329
-
330
+
330
331
  def get_load_balancer_monthly_cost(self, lb_type: str = "application", region: str = "us-east-1") -> float:
331
332
  """
332
333
  Get Load Balancer monthly cost.
333
-
334
+
334
335
  Args:
335
336
  lb_type: Load balancer type (application, network, gateway)
336
337
  region: AWS region
337
-
338
+
338
339
  Returns:
339
340
  Monthly cost in USD
340
341
  """
341
342
  result = self.get_service_pricing(f"loadbalancer_{lb_type}", region)
342
343
  return result.monthly_cost
343
-
344
- def get_rds_instance_monthly_cost(self, instance_class: str, engine: str = "mysql", region: str = "us-east-1") -> float:
344
+
345
+ def get_rds_instance_monthly_cost(
346
+ self, instance_class: str, engine: str = "mysql", region: str = "us-east-1"
347
+ ) -> float:
345
348
  """
346
349
  Get RDS instance monthly cost.
347
-
350
+
348
351
  Args:
349
352
  instance_class: RDS instance class (e.g., db.t3.micro)
350
353
  engine: Database engine (mysql, postgres, oracle, etc.)
351
354
  region: AWS region
352
-
355
+
353
356
  Returns:
354
357
  Monthly cost in USD
355
358
  """
356
359
  result = self.get_service_pricing(f"rds_{engine}_{instance_class}", region)
357
360
  return result.monthly_cost
358
-
361
+
359
362
  # ============================================================================
360
363
  # ENTERPRISE PERFORMANCE METHODS - <1s Response Time Requirements
361
364
  # ============================================================================
362
-
363
- def get_multi_service_pricing(self, service_requests: List[Tuple[str, str]], max_workers: int = 4) -> Dict[str, AWSPricingResult]:
365
+
366
+ def get_multi_service_pricing(
367
+ self, service_requests: List[Tuple[str, str]], max_workers: int = 4
368
+ ) -> Dict[str, AWSPricingResult]:
364
369
  """
365
370
  Get pricing for multiple services concurrently for enterprise performance.
366
-
371
+
367
372
  Args:
368
373
  service_requests: List of (service_key, region) tuples
369
374
  max_workers: Maximum concurrent workers
370
-
375
+
371
376
  Returns:
372
377
  Dictionary mapping service_key:region to AWSPricingResult
373
378
  """
374
379
  results = {}
375
-
380
+
376
381
  def fetch_pricing(service_request):
377
382
  service_key, region = service_request
378
383
  try:
@@ -380,13 +385,11 @@ class DynamicAWSPricing:
380
385
  except Exception as e:
381
386
  logger.error(f"Failed to fetch pricing for {service_key} in {region}: {e}")
382
387
  return f"{service_key}:{region}", None
383
-
388
+
384
389
  # Use existing executor for thread management
385
390
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
386
- future_to_service = {
387
- executor.submit(fetch_pricing, req): req for req in service_requests
388
- }
389
-
391
+ future_to_service = {executor.submit(fetch_pricing, req): req for req in service_requests}
392
+
390
393
  for future in as_completed(future_to_service):
391
394
  service_request = future_to_service[future]
392
395
  try:
@@ -396,30 +399,35 @@ class DynamicAWSPricing:
396
399
  except Exception as e:
397
400
  service_key, region = service_request
398
401
  logger.error(f"Concurrent pricing fetch failed for {service_key}:{region}: {e}")
399
-
402
+
400
403
  return results
401
-
404
+
402
405
  def warm_cache_for_region(self, region: str, services: Optional[List[str]] = None) -> None:
403
406
  """
404
407
  Pre-warm pricing cache for a region to ensure <1s response times.
405
-
408
+
406
409
  Args:
407
410
  region: AWS region to warm cache for
408
411
  services: List of services to warm (default: common services)
409
412
  """
410
413
  if services is None:
411
414
  services = [
412
- "ec2_instance", "elastic_ip", "nat_gateway", "ebs_gp3",
413
- "vpc_endpoint", "transit_gateway", "loadbalancer_application"
415
+ "ec2_instance",
416
+ "elastic_ip",
417
+ "nat_gateway",
418
+ "ebs_gp3",
419
+ "vpc_endpoint",
420
+ "transit_gateway",
421
+ "loadbalancer_application",
414
422
  ]
415
-
423
+
416
424
  service_requests = [(service, region) for service in services]
417
-
425
+
418
426
  console.print(f"[dim]Warming pricing cache for {region} with {len(services)} services...[/]")
419
427
  start_time = time.time()
420
-
428
+
421
429
  self.get_multi_service_pricing(service_requests)
422
-
430
+
423
431
  elapsed = time.time() - start_time
424
432
  console.print(f"[dim]Cache warming completed in {elapsed:.2f}s[/]")
425
433
  logger.info(f"Pricing cache warmed for {region} in {elapsed:.2f}s")
@@ -427,141 +435,141 @@ class DynamicAWSPricing:
427
435
  def _get_ec2_fallback_pricing(self, instance_type: str, region: str) -> AWSPricingResult:
428
436
  """
429
437
  ENTERPRISE CRITICAL: EC2 fallback pricing for absolute last resort.
430
-
438
+
431
439
  Args:
432
440
  instance_type: EC2 instance type
433
441
  region: AWS region
434
-
442
+
435
443
  Returns:
436
444
  AWSPricingResult with estimated pricing
437
445
  """
438
446
  console.print(f"[red]⚠ ENTERPRISE WARNING: Using fallback pricing for EC2 {instance_type}[/red]")
439
-
447
+
440
448
  # Calculate base hourly rate from AWS documentation patterns
441
449
  hourly_rate = self._calculate_ec2_from_aws_patterns(instance_type)
442
-
450
+
443
451
  if hourly_rate <= 0:
444
452
  raise RuntimeError(
445
453
  f"ENTERPRISE VIOLATION: No dynamic pricing available for {instance_type} "
446
454
  f"in region {region}. Cannot proceed without hardcoded values."
447
455
  )
448
-
456
+
449
457
  # Apply dynamic regional multiplier from AWS Pricing API
450
458
  region_multiplier = self.get_regional_pricing_multiplier("ec2_instance", region, "us-east-1")
451
459
  adjusted_hourly_rate = hourly_rate * region_multiplier
452
460
  monthly_cost = adjusted_hourly_rate * 24 * 30
453
-
461
+
454
462
  logger.warning(f"Using calculated EC2 fallback for {instance_type} in {region}: ${monthly_cost:.4f}/month")
455
-
463
+
456
464
  return AWSPricingResult(
457
465
  service_key=f"ec2_instance:{instance_type}",
458
466
  region=region,
459
467
  monthly_cost=monthly_cost,
460
468
  pricing_source="calculated_fallback",
461
469
  last_updated=datetime.now(),
462
- currency="USD"
470
+ currency="USD",
463
471
  )
464
472
 
465
473
  def _calculate_ec2_from_aws_patterns(self, instance_type: str) -> float:
466
474
  """
467
475
  Calculate EC2 pricing using AWS documented patterns and ratios.
468
-
476
+
469
477
  Based on AWS instance family patterns, not hardcoded business values.
470
-
478
+
471
479
  Returns:
472
480
  Hourly rate or 0 if cannot be calculated
473
481
  """
474
482
  instance_type = instance_type.lower()
475
-
483
+
476
484
  # Parse instance type (e.g., "t3.micro" -> family="t3", size="micro")
477
485
  try:
478
- family, size = instance_type.split('.', 1)
486
+ family, size = instance_type.split(".", 1)
479
487
  except ValueError:
480
488
  logger.error(f"Invalid instance type format: {instance_type}")
481
489
  return 0.0
482
-
490
+
483
491
  # Instance family base rates from AWS pricing patterns
484
492
  # These represent documented relative pricing, not hardcoded business values
485
493
  family_base_factors = {
486
- 't3': 1.0, # Burstable performance baseline
487
- 't2': 1.12, # Previous generation, slightly higher
488
- 'm5': 1.85, # General purpose, balanced
489
- 'c5': 1.63, # Compute optimized
490
- 'r5': 2.42, # Memory optimized
491
- 'm4': 1.75, # Previous generation general purpose
492
- 'c4': 1.54, # Previous generation compute
493
- 'r4': 2.28, # Previous generation memory
494
+ "t3": 1.0, # Burstable performance baseline
495
+ "t2": 1.12, # Previous generation, slightly higher
496
+ "m5": 1.85, # General purpose, balanced
497
+ "c5": 1.63, # Compute optimized
498
+ "r5": 2.42, # Memory optimized
499
+ "m4": 1.75, # Previous generation general purpose
500
+ "c4": 1.54, # Previous generation compute
501
+ "r4": 2.28, # Previous generation memory
494
502
  }
495
-
503
+
496
504
  # Size multipliers based on AWS documented scaling
497
505
  size_multipliers = {
498
- 'nano': 0.25, # Quarter of micro
499
- 'micro': 1.0, # Base unit
500
- 'small': 2.0, # Double micro
501
- 'medium': 4.0, # Double small
502
- 'large': 8.0, # Double medium
503
- 'xlarge': 16.0, # Double large
504
- '2xlarge': 32.0, # Double xlarge
505
- '4xlarge': 64.0, # Double 2xlarge
506
+ "nano": 0.25, # Quarter of micro
507
+ "micro": 1.0, # Base unit
508
+ "small": 2.0, # Double micro
509
+ "medium": 4.0, # Double small
510
+ "large": 8.0, # Double medium
511
+ "xlarge": 16.0, # Double large
512
+ "2xlarge": 32.0, # Double xlarge
513
+ "4xlarge": 64.0, # Double 2xlarge
506
514
  }
507
-
515
+
508
516
  family_factor = family_base_factors.get(family, 0.0)
509
517
  size_multiplier = size_multipliers.get(size, 0.0)
510
-
518
+
511
519
  if family_factor == 0.0:
512
520
  logger.warning(f"Unknown EC2 family: {family}")
513
521
  return 0.0
514
-
522
+
515
523
  if size_multiplier == 0.0:
516
524
  logger.warning(f"Unknown EC2 size: {size}")
517
525
  return 0.0
518
-
526
+
519
527
  # Calculate using AWS documented scaling patterns
520
528
  # Instead of hardcoded baseline, use the family and size factors
521
529
  # This calculates relative pricing without hardcoded base rates
522
-
530
+
523
531
  # Use the smallest family factor as baseline to avoid hardcoded values
524
532
  baseline_factor = min(family_base_factors.values()) # t3 = 1.0
525
-
533
+
526
534
  # Try to get real baseline pricing from AWS API for any known instance type
527
535
  baseline_rate = None
528
- known_instance_types = ['t3.micro', 't2.micro', 'm5.large']
529
-
536
+ known_instance_types = ["t3.micro", "t2.micro", "m5.large"]
537
+
530
538
  for baseline_instance in known_instance_types:
531
539
  try:
532
540
  pricing_engine = get_aws_pricing_engine(enable_fallback=False, profile=self.profile)
533
- real_pricing = pricing_engine._get_ec2_api_pricing(baseline_instance, 'us-east-1')
541
+ real_pricing = pricing_engine._get_ec2_api_pricing(baseline_instance, "us-east-1")
534
542
  baseline_rate = real_pricing.monthly_cost / (24 * 30) # Convert to hourly
535
543
  logger.info(f"Using {baseline_instance} as baseline: ${baseline_rate}/hour")
536
544
  break
537
545
  except Exception as e:
538
546
  logger.debug(f"Could not get pricing for {baseline_instance}: {e}")
539
547
  continue
540
-
548
+
541
549
  if baseline_rate is None:
542
550
  # If we can't get any real pricing, we cannot calculate reliably
543
551
  logger.error(f"ENTERPRISE COMPLIANCE: Cannot calculate {instance_type} without AWS API baseline")
544
552
  return 0.0
545
-
553
+
546
554
  # Calculate relative pricing based on AWS documented ratios and real baseline
547
555
  calculated_rate = baseline_rate * family_factor * size_multiplier
548
-
556
+
549
557
  logger.info(f"Calculated {instance_type} rate: ${calculated_rate}/hour using AWS patterns")
550
558
  return calculated_rate
551
559
 
552
560
  def get_service_pricing(self, service_key: str, region: str = "us-east-1") -> AWSPricingResult:
553
561
  """
554
562
  Get dynamic pricing for AWS service.
555
-
563
+
556
564
  Args:
557
565
  service_key: Service identifier (vpc, nat_gateway, elastic_ip, etc.)
558
566
  region: AWS region for pricing lookup
559
-
567
+
560
568
  Returns:
561
569
  AWSPricingResult with current pricing information
562
570
  """
563
571
  cache_key = f"{service_key}:{region}"
564
-
572
+
565
573
  with self._cache_lock:
566
574
  # Check cache first
567
575
  if cache_key in self._pricing_cache:
@@ -572,20 +580,20 @@ class DynamicAWSPricing:
572
580
  else:
573
581
  # Cache expired, remove it
574
582
  del self._pricing_cache[cache_key]
575
-
583
+
576
584
  # Try to get real pricing from AWS API
577
585
  try:
578
586
  pricing_result = self._get_aws_api_pricing(service_key, region)
579
-
587
+
580
588
  # Cache the result
581
589
  with self._cache_lock:
582
590
  self._pricing_cache[cache_key] = pricing_result
583
-
591
+
584
592
  return pricing_result
585
-
593
+
586
594
  except Exception as e:
587
595
  logger.error(f"Failed to get AWS API pricing for {service_key}: {e}")
588
-
596
+
589
597
  if self.enable_fallback:
590
598
  return self._get_fallback_pricing(service_key, region)
591
599
  else:
@@ -612,21 +620,21 @@ class DynamicAWSPricing:
612
620
  # Use enhanced session management for universal AWS environment support
613
621
  if self.profile:
614
622
  # Use profile-aware session creation with proper credential resolution
615
- session = create_cost_session(self.profile)
616
- pricing_client = session.client('pricing', region_name='us-east-1')
623
+ session = create_cost_session(profile_name=self.profile)
624
+ pricing_client = session.client("pricing", region_name="us-east-1")
617
625
  logger.debug(f"Created pricing client with profile: {self.profile}")
618
626
  else:
619
627
  # Try environment-based credentials with fallback chain
620
628
  try:
621
629
  # First attempt: Use default credential chain
622
- pricing_client = boto3.client('pricing', region_name='us-east-1')
630
+ pricing_client = boto3.client("pricing", region_name="us-east-1")
623
631
  logger.debug("Created pricing client with default credentials")
624
632
  except NoCredentialsError:
625
633
  # Second attempt: Try with AWS_PROFILE if set
626
- aws_profile = os.getenv('AWS_PROFILE')
634
+ aws_profile = os.getenv("AWS_PROFILE")
627
635
  if aws_profile:
628
636
  session = boto3.Session(profile_name=aws_profile)
629
- pricing_client = session.client('pricing', region_name='us-east-1')
637
+ pricing_client = session.client("pricing", region_name="us-east-1")
630
638
  logger.debug(f"Created pricing client with AWS_PROFILE: {aws_profile}")
631
639
  else:
632
640
  # Enhanced credential guidance
@@ -636,7 +644,7 @@ class DynamicAWSPricing:
636
644
  console.print(" 2. AWS SSO: [cyan]aws sso login --profile your-profile[/]")
637
645
  console.print(" 3. Environment: [cyan]export AWS_ACCESS_KEY_ID=...[/]")
638
646
  raise NoCredentialsError("No AWS credentials available for Pricing API")
639
-
647
+
640
648
  # Enterprise Service Mapping for AWS Pricing API - Complete Coverage
641
649
  service_mapping = {
642
650
  # Core Networking Services - NAT Gateway (fallback to broad search)
@@ -645,33 +653,32 @@ class DynamicAWSPricing:
645
653
  "location": self._get_aws_location_name(region),
646
654
  "filters": [
647
655
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
648
- ] # Simplified - will search for NAT Gateway in response
656
+ ], # Simplified - will search for NAT Gateway in response
649
657
  },
650
658
  "elastic_ip": {
651
- "service_code": "AmazonEC2",
659
+ "service_code": "AmazonEC2",
652
660
  "location": self._get_aws_location_name(region),
653
661
  "filters": [
654
662
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
655
- {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "IP Address"}
656
- ]
663
+ {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "IP Address"},
664
+ ],
657
665
  },
658
666
  "vpc_endpoint": {
659
667
  "service_code": "AmazonVPC",
660
668
  "location": self._get_aws_location_name(region),
661
669
  "filters": [
662
670
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
663
- {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "VpcEndpoint"}
664
- ]
671
+ {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "VpcEndpoint"},
672
+ ],
665
673
  },
666
674
  "transit_gateway": {
667
675
  "service_code": "AmazonVPC",
668
676
  "location": self._get_aws_location_name(region),
669
677
  "filters": [
670
678
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
671
- {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Transit Gateway"}
672
- ]
679
+ {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Transit Gateway"},
680
+ ],
673
681
  },
674
-
675
682
  # Compute Services
676
683
  "ec2_instance": {
677
684
  "service_code": "AmazonEC2",
@@ -680,10 +687,9 @@ class DynamicAWSPricing:
680
687
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
681
688
  {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Compute Instance"},
682
689
  {"Type": "TERM_MATCH", "Field": "tenancy", "Value": "Shared"},
683
- {"Type": "TERM_MATCH", "Field": "operatingSystem", "Value": "Linux"}
684
- ]
690
+ {"Type": "TERM_MATCH", "Field": "operatingSystem", "Value": "Linux"},
691
+ ],
685
692
  },
686
-
687
693
  # Storage Services
688
694
  "ebs_gp3": {
689
695
  "service_code": "AmazonEC2",
@@ -692,8 +698,8 @@ class DynamicAWSPricing:
692
698
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
693
699
  {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Storage"},
694
700
  {"Type": "TERM_MATCH", "Field": "volumeType", "Value": "General Purpose"},
695
- {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "gp3"}
696
- ]
701
+ {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "gp3"},
702
+ ],
697
703
  },
698
704
  "ebs_gp2": {
699
705
  "service_code": "AmazonEC2",
@@ -702,8 +708,8 @@ class DynamicAWSPricing:
702
708
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
703
709
  {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Storage"},
704
710
  {"Type": "TERM_MATCH", "Field": "volumeType", "Value": "General Purpose"},
705
- {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "gp2"}
706
- ]
711
+ {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "gp2"},
712
+ ],
707
713
  },
708
714
  "ebs_io1": {
709
715
  "service_code": "AmazonEC2",
@@ -712,8 +718,8 @@ class DynamicAWSPricing:
712
718
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
713
719
  {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Storage"},
714
720
  {"Type": "TERM_MATCH", "Field": "volumeType", "Value": "Provisioned IOPS"},
715
- {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "io1"}
716
- ]
721
+ {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "io1"},
722
+ ],
717
723
  },
718
724
  "ebs_io2": {
719
725
  "service_code": "AmazonEC2",
@@ -722,44 +728,43 @@ class DynamicAWSPricing:
722
728
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
723
729
  {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Storage"},
724
730
  {"Type": "TERM_MATCH", "Field": "volumeType", "Value": "Provisioned IOPS"},
725
- {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "io2"}
726
- ]
731
+ {"Type": "TERM_MATCH", "Field": "volumeApiName", "Value": "io2"},
732
+ ],
727
733
  },
728
-
729
734
  # Load Balancer Services
730
735
  "loadbalancer_application": {
731
736
  "service_code": "AWSELB",
732
737
  "location": self._get_aws_location_name(region),
733
738
  "filters": [
734
739
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
735
- {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Load Balancer-Application"}
736
- ]
740
+ {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Load Balancer-Application"},
741
+ ],
737
742
  },
738
743
  "loadbalancer_network": {
739
744
  "service_code": "AWSELB",
740
745
  "location": self._get_aws_location_name(region),
741
746
  "filters": [
742
747
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
743
- {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Load Balancer-Network"}
744
- ]
748
+ {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Load Balancer-Network"},
749
+ ],
745
750
  },
746
751
  "loadbalancer_gateway": {
747
752
  "service_code": "AWSELB",
748
753
  "location": self._get_aws_location_name(region),
749
754
  "filters": [
750
755
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
751
- {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Load Balancer-Gateway"}
752
- ]
753
- }
756
+ {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Load Balancer-Gateway"},
757
+ ],
758
+ },
754
759
  }
755
-
760
+
756
761
  # Handle dynamic RDS service keys (rds_engine_instanceclass)
757
762
  if service_key.startswith("rds_"):
758
763
  parts = service_key.split("_")
759
764
  if len(parts) >= 3:
760
765
  engine = parts[1]
761
766
  instance_class = "_".join(parts[2:])
762
-
767
+
763
768
  service_mapping[service_key] = {
764
769
  "service_code": "AmazonRDS",
765
770
  "location": self._get_aws_location_name(region),
@@ -767,10 +772,10 @@ class DynamicAWSPricing:
767
772
  {"Type": "TERM_MATCH", "Field": "location", "Value": self._get_aws_location_name(region)},
768
773
  {"Type": "TERM_MATCH", "Field": "productFamily", "Value": "Database Instance"},
769
774
  {"Type": "TERM_MATCH", "Field": "databaseEngine", "Value": engine.title()},
770
- {"Type": "TERM_MATCH", "Field": "instanceType", "Value": instance_class}
771
- ]
775
+ {"Type": "TERM_MATCH", "Field": "instanceType", "Value": instance_class},
776
+ ],
772
777
  }
773
-
778
+
774
779
  # Handle data_transfer service with graceful fallback
775
780
  if service_key == "data_transfer":
776
781
  print_warning("data_transfer service not supported by AWS Pricing API - using standard rates")
@@ -780,43 +785,43 @@ class DynamicAWSPricing:
780
785
  region=region,
781
786
  monthly_cost=0.045, # $0.045/GB for NAT Gateway data processing
782
787
  pricing_source="aws_standard_rates",
783
- last_updated=datetime.now()
788
+ last_updated=datetime.now(),
784
789
  )
785
790
 
786
791
  if service_key not in service_mapping:
787
792
  raise ValueError(f"Service {service_key} not supported by AWS Pricing API integration")
788
-
793
+
789
794
  service_info = service_mapping[service_key]
790
-
795
+
791
796
  # Query AWS Pricing API
792
797
  response = pricing_client.get_products(
793
798
  ServiceCode=service_info["service_code"],
794
799
  Filters=service_info["filters"],
795
- MaxResults=5 # Get more results to find best match
800
+ MaxResults=5, # Get more results to find best match
796
801
  )
797
-
798
- if not response.get('PriceList'):
802
+
803
+ if not response.get("PriceList"):
799
804
  raise ValueError(f"No pricing data found for {service_key} in {region}")
800
-
805
+
801
806
  # Extract pricing from response with service-specific filtering
802
807
  hourly_rate = None
803
808
 
804
- for price_item in response['PriceList']:
809
+ for price_item in response["PriceList"]:
805
810
  try:
806
811
  price_data = json.loads(price_item)
807
- product = price_data.get('product', {})
808
- attributes = product.get('attributes', {})
812
+ product = price_data.get("product", {})
813
+ attributes = product.get("attributes", {})
809
814
 
810
815
  # Service-specific filtering for broad searches
811
816
  if service_key == "nat_gateway":
812
817
  # Look for NAT Gateway specific attributes
813
818
  item_text = json.dumps(attributes).lower()
814
- if not any(keyword in item_text for keyword in ['nat', 'natgateway', 'nat-gateway']):
819
+ if not any(keyword in item_text for keyword in ["nat", "natgateway", "nat-gateway"]):
815
820
  continue # Skip items that don't contain NAT references
816
821
 
817
822
  # Navigate the pricing structure
818
- terms = price_data.get('terms', {})
819
- on_demand = terms.get('OnDemand', {})
823
+ terms = price_data.get("terms", {})
824
+ on_demand = terms.get("OnDemand", {})
820
825
 
821
826
  if not on_demand:
822
827
  continue
@@ -825,7 +830,7 @@ class DynamicAWSPricing:
825
830
  term_key = list(on_demand.keys())[0]
826
831
  term_data = on_demand[term_key]
827
832
 
828
- price_dimensions = term_data.get('priceDimensions', {})
833
+ price_dimensions = term_data.get("priceDimensions", {})
829
834
  if not price_dimensions:
830
835
  continue
831
836
 
@@ -833,15 +838,17 @@ class DynamicAWSPricing:
833
838
  price_dim_key = list(price_dimensions.keys())[0]
834
839
  price_dim = price_dimensions[price_dim_key]
835
840
 
836
- price_per_unit = price_dim.get('pricePerUnit', {})
837
- usd_price = price_per_unit.get('USD')
841
+ price_per_unit = price_dim.get("pricePerUnit", {})
842
+ usd_price = price_per_unit.get("USD")
838
843
 
839
- if usd_price and usd_price != '0.0000000000':
844
+ if usd_price and usd_price != "0.0000000000":
840
845
  hourly_rate = float(usd_price)
841
846
  monthly_cost = hourly_rate * 24 * 30
842
847
 
843
848
  # Honest success reporting
844
- console.print(f"[green]✅ Real-time AWS API pricing[/]: {service_key} = ${monthly_cost:.2f}/month")
849
+ console.print(
850
+ f"[green]✅ Real-time AWS API pricing[/]: {service_key} = ${monthly_cost:.2f}/month"
851
+ )
845
852
  logger.info(f"Found AWS API pricing for {service_key}: ${hourly_rate}/hour")
846
853
 
847
854
  # Log what we found for debugging
@@ -852,24 +859,24 @@ class DynamicAWSPricing:
852
859
  except (KeyError, ValueError, IndexError, json.JSONDecodeError) as parse_error:
853
860
  logger.debug(f"Failed to parse pricing data: {parse_error}")
854
861
  continue
855
-
862
+
856
863
  if hourly_rate is None:
857
864
  raise ValueError(f"Could not extract valid pricing for {service_key}")
858
-
865
+
859
866
  # Convert hourly to monthly (24 hours * 30 days)
860
867
  monthly_cost = hourly_rate * 24 * 30
861
-
868
+
862
869
  logger.info(f"AWS API pricing for {service_key} in {region}: ${monthly_cost:.4f}/month")
863
-
870
+
864
871
  return AWSPricingResult(
865
872
  service_key=service_key,
866
873
  region=region,
867
874
  monthly_cost=monthly_cost,
868
875
  pricing_source="aws_api",
869
876
  last_updated=datetime.now(),
870
- currency="USD"
877
+ currency="USD",
871
878
  )
872
-
879
+
873
880
  except (ClientError, NoCredentialsError) as e:
874
881
  logger.warning(f"AWS Pricing API unavailable for {service_key}: {e}")
875
882
  raise e
@@ -906,9 +913,9 @@ class DynamicAWSPricing:
906
913
  monthly_cost=override_cost,
907
914
  pricing_source="environment_override",
908
915
  last_updated=datetime.now(),
909
- currency="USD"
916
+ currency="USD",
910
917
  )
911
-
918
+
912
919
  # Try alternative approach: Query public AWS docs or use Cloud Formation cost estimation
913
920
  try:
914
921
  estimated_cost = self._query_alternative_pricing_sources(service_key, region)
@@ -919,22 +926,22 @@ class DynamicAWSPricing:
919
926
  monthly_cost=estimated_cost,
920
927
  pricing_source="alternative_source",
921
928
  last_updated=datetime.now(),
922
- currency="USD"
929
+ currency="USD",
923
930
  )
924
931
  except Exception as e:
925
932
  logger.debug(f"Alternative pricing source failed: {e}")
926
-
933
+
927
934
  # LAST RESORT: Calculated estimates from AWS documentation
928
935
  # These are NOT hardcoded business values but technical calculations
929
936
  # Based on AWS official documentation and calculator methodology
930
937
  base_hourly_rates_from_aws_docs = self._calculate_from_aws_documentation(service_key)
931
-
938
+
932
939
  if not base_hourly_rates_from_aws_docs:
933
940
  raise RuntimeError(
934
941
  f"ENTERPRISE VIOLATION: No dynamic pricing available for {service_key} "
935
942
  f"in region {region}. Cannot proceed without hardcoded values."
936
943
  )
937
-
944
+
938
945
  # Apply dynamic regional multiplier from AWS Pricing API
939
946
  region_multiplier = self.get_regional_pricing_multiplier(service_key, region, "us-east-1")
940
947
  hourly_rate = base_hourly_rates_from_aws_docs * region_multiplier
@@ -951,7 +958,7 @@ class DynamicAWSPricing:
951
958
  monthly_cost=monthly_cost,
952
959
  pricing_source="standard_aws_rate",
953
960
  last_updated=datetime.now(),
954
- currency="USD"
961
+ currency="USD",
955
962
  )
956
963
 
957
964
  def _check_pricing_overrides(self, service_key: str, region: str) -> float:
@@ -1004,12 +1011,9 @@ class DynamicAWSPricing:
1004
1011
  # PRIORITY 1: Check for historical cached data from other regions
1005
1012
  with self._cache_lock:
1006
1013
  for cache_key, cached_result in self._pricing_cache.items():
1007
- if (cached_result.service_key == service_key and
1008
- cached_result.pricing_source == "aws_api"):
1014
+ if cached_result.service_key == service_key and cached_result.pricing_source == "aws_api":
1009
1015
  # Found historical AWS API data, apply regional multiplier
1010
- multiplier = self.get_regional_pricing_multiplier(
1011
- service_key, region, cached_result.region
1012
- )
1016
+ multiplier = self.get_regional_pricing_multiplier(service_key, region, cached_result.region)
1013
1017
  estimated_cost = cached_result.monthly_cost * multiplier
1014
1018
  logger.info(f"Using historical pricing data for {service_key}: ${estimated_cost}/month")
1015
1019
  console.print(f"[blue]ℹ Using historical AWS API data with regional adjustment[/]")
@@ -1022,7 +1026,9 @@ class DynamicAWSPricing:
1022
1026
  us_east_pricing = self._get_aws_api_pricing(service_key, "us-east-1")
1023
1027
  multiplier = self.get_regional_pricing_multiplier(service_key, region, "us-east-1")
1024
1028
  estimated_cost = us_east_pricing.monthly_cost * multiplier
1025
- logger.info(f"Using us-east-1 pricing with regional multiplier for {service_key}: ${estimated_cost}/month")
1029
+ logger.info(
1030
+ f"Using us-east-1 pricing with regional multiplier for {service_key}: ${estimated_cost}/month"
1031
+ )
1026
1032
  console.print(f"[blue]ℹ Using us-east-1 pricing with {multiplier:.3f}x regional adjustment[/]")
1027
1033
  return estimated_cost
1028
1034
  except Exception as e:
@@ -1053,10 +1059,10 @@ class DynamicAWSPricing:
1053
1059
  # Standard AWS rates from pricing documentation (us-east-1)
1054
1060
  # These are NOT hardcoded business values but technical reference rates
1055
1061
  aws_documented_hourly_rates = {
1056
- "nat_gateway": 0.045, # AWS standard NAT Gateway rate
1057
- "elastic_ip": 0.005, # AWS standard idle EIP rate
1058
- "vpc_endpoint": 0.01, # AWS standard interface endpoint rate
1059
- "transit_gateway": 0.05, # AWS standard Transit Gateway rate
1062
+ "nat_gateway": 0.045, # AWS standard NAT Gateway rate
1063
+ "elastic_ip": 0.005, # AWS standard idle EIP rate
1064
+ "vpc_endpoint": 0.01, # AWS standard interface endpoint rate
1065
+ "transit_gateway": 0.05, # AWS standard Transit Gateway rate
1060
1066
  "ebs_gp3": 0.08 / (24 * 30), # AWS standard GP3 per GB/month to hourly
1061
1067
  "ebs_gp2": 0.10 / (24 * 30), # AWS standard GP2 per GB/month to hourly
1062
1068
  }
@@ -1075,10 +1081,10 @@ class DynamicAWSPricing:
1075
1081
  def _get_aws_location_name(self, region: str) -> str:
1076
1082
  """
1077
1083
  Convert AWS region code to location name used by Pricing API.
1078
-
1084
+
1079
1085
  Args:
1080
1086
  region: AWS region code
1081
-
1087
+
1082
1088
  Returns:
1083
1089
  AWS location name for Pricing API
1084
1090
  """
@@ -1089,7 +1095,6 @@ class DynamicAWSPricing:
1089
1095
  "us-east-2": "US East (Ohio)",
1090
1096
  "us-west-1": "US West (N. California)",
1091
1097
  "us-west-2": "US West (Oregon)",
1092
-
1093
1098
  # EU Regions
1094
1099
  "eu-central-1": "Europe (Frankfurt)",
1095
1100
  "eu-central-2": "Europe (Zurich)",
@@ -1099,7 +1104,6 @@ class DynamicAWSPricing:
1099
1104
  "eu-south-1": "Europe (Milan)",
1100
1105
  "eu-south-2": "Europe (Spain)",
1101
1106
  "eu-north-1": "Europe (Stockholm)",
1102
-
1103
1107
  # Asia Pacific Regions
1104
1108
  "ap-northeast-1": "Asia Pacific (Tokyo)",
1105
1109
  "ap-northeast-2": "Asia Pacific (Seoul)",
@@ -1111,7 +1115,6 @@ class DynamicAWSPricing:
1111
1115
  "ap-south-1": "Asia Pacific (Mumbai)",
1112
1116
  "ap-south-2": "Asia Pacific (Hyderabad)",
1113
1117
  "ap-east-1": "Asia Pacific (Hong Kong)",
1114
-
1115
1118
  # Other Regions
1116
1119
  "ca-central-1": "Canada (Central)",
1117
1120
  "ca-west-1": "Canada (West)",
@@ -1119,69 +1122,69 @@ class DynamicAWSPricing:
1119
1122
  "af-south-1": "Africa (Cape Town)",
1120
1123
  "me-south-1": "Middle East (Bahrain)",
1121
1124
  "me-central-1": "Middle East (UAE)",
1122
-
1123
1125
  # GovCloud
1124
1126
  "us-gov-east-1": "AWS GovCloud (US-East)",
1125
1127
  "us-gov-west-1": "AWS GovCloud (US-West)",
1126
-
1127
1128
  # China (Note: Pricing API may not be available)
1128
1129
  "cn-north-1": "China (Beijing)",
1129
1130
  "cn-northwest-1": "China (Ningxia)",
1130
1131
  }
1131
-
1132
+
1132
1133
  return location_mapping.get(region, "US East (N. Virginia)")
1133
1134
 
1134
- def get_regional_pricing_multiplier(self, service_key: str, target_region: str, base_region: str = "us-east-1") -> float:
1135
+ def get_regional_pricing_multiplier(
1136
+ self, service_key: str, target_region: str, base_region: str = "us-east-1"
1137
+ ) -> float:
1135
1138
  """
1136
1139
  Get regional pricing multiplier by comparing real AWS pricing between regions.
1137
-
1140
+
1138
1141
  Args:
1139
1142
  service_key: Service identifier (nat_gateway, elastic_ip, etc.)
1140
1143
  target_region: Target region to get multiplier for
1141
1144
  base_region: Base region for comparison (default us-east-1)
1142
-
1145
+
1143
1146
  Returns:
1144
1147
  Regional pricing multiplier (target_price / base_price)
1145
1148
  """
1146
1149
  cache_key = f"{service_key}:{target_region}:{base_region}"
1147
-
1150
+
1148
1151
  with self._region_cache_lock:
1149
1152
  # Check cache first
1150
1153
  if cache_key in self._regional_pricing_cache:
1151
1154
  cached_result = self._regional_pricing_cache[cache_key]
1152
- if datetime.now() - cached_result['last_updated'] < self.cache_ttl:
1155
+ if datetime.now() - cached_result["last_updated"] < self.cache_ttl:
1153
1156
  logger.debug(f"Using cached regional multiplier for {service_key} {target_region}")
1154
- return cached_result['multiplier']
1157
+ return cached_result["multiplier"]
1155
1158
  else:
1156
1159
  # Cache expired, remove it
1157
1160
  del self._regional_pricing_cache[cache_key]
1158
-
1161
+
1159
1162
  try:
1160
1163
  # Get real pricing for both regions
1161
1164
  base_pricing = self._get_aws_api_pricing(service_key, base_region)
1162
1165
  target_pricing = self._get_aws_api_pricing(service_key, target_region)
1163
-
1166
+
1164
1167
  # Calculate multiplier
1165
1168
  if base_pricing.monthly_cost > 0:
1166
1169
  multiplier = target_pricing.monthly_cost / base_pricing.monthly_cost
1167
1170
  else:
1168
1171
  multiplier = 1.0
1169
-
1172
+
1170
1173
  # Cache the result
1171
1174
  with self._region_cache_lock:
1172
1175
  self._regional_pricing_cache[cache_key] = {
1173
- 'multiplier': multiplier,
1174
- 'last_updated': datetime.now(),
1175
- 'base_cost': base_pricing.monthly_cost,
1176
- 'target_cost': target_pricing.monthly_cost
1176
+ "multiplier": multiplier,
1177
+ "last_updated": datetime.now(),
1178
+ "base_cost": base_pricing.monthly_cost,
1179
+ "target_cost": target_pricing.monthly_cost,
1177
1180
  }
1178
-
1181
+
1179
1182
  logger.info(f"Regional multiplier for {service_key} {target_region}: {multiplier:.4f}")
1180
1183
  return multiplier
1181
-
1184
+
1182
1185
  except Exception as e:
1183
1186
  logger.warning(f"Failed to get regional pricing multiplier for {service_key} {target_region}: {e}")
1184
-
1187
+
1185
1188
  # Fallback: Return 1.0 (no multiplier) to avoid hardcoded values
1186
1189
  logger.warning(f"Using 1.0 multiplier for {service_key} {target_region} - investigate pricing API access")
1187
1190
  return 1.0
@@ -1192,44 +1195,54 @@ class DynamicAWSPricing:
1192
1195
  total_entries = len(self._pricing_cache)
1193
1196
  api_entries = sum(1 for r in self._pricing_cache.values() if r.pricing_source == "aws_api")
1194
1197
  fallback_entries = sum(1 for r in self._pricing_cache.values() if r.pricing_source == "fallback")
1195
-
1198
+
1196
1199
  return {
1197
1200
  "total_cached_entries": total_entries,
1198
1201
  "aws_api_entries": api_entries,
1199
1202
  "fallback_entries": fallback_entries,
1200
1203
  "cache_hit_rate": (api_entries / total_entries * 100) if total_entries > 0 else 0,
1201
- "cache_ttl_hours": self.cache_ttl.total_seconds() / 3600
1204
+ "cache_ttl_hours": self.cache_ttl.total_seconds() / 3600,
1202
1205
  }
1203
1206
 
1204
1207
  def get_available_regions(self) -> List[str]:
1205
1208
  """
1206
1209
  Get all available AWS regions dynamically from AWS API.
1207
-
1210
+
1208
1211
  Returns:
1209
1212
  List of AWS region codes
1210
1213
  """
1211
1214
  try:
1212
1215
  if self.profile:
1213
- session = create_cost_session(self.profile)
1214
- ec2_client = session.client('ec2', region_name='us-east-1')
1216
+ session = create_cost_session(profile_name=self.profile)
1217
+ ec2_client = session.client("ec2", region_name="us-east-1")
1215
1218
  else:
1216
- ec2_client = boto3.client('ec2', region_name='us-east-1')
1217
-
1219
+ ec2_client = boto3.client("ec2", region_name="us-east-1")
1220
+
1218
1221
  response = ec2_client.describe_regions()
1219
- regions = [region['RegionName'] for region in response['Regions']]
1220
-
1222
+ regions = [region["RegionName"] for region in response["Regions"]]
1223
+
1221
1224
  logger.info(f"Retrieved {len(regions)} AWS regions from API")
1222
1225
  return sorted(regions)
1223
-
1226
+
1224
1227
  except Exception as e:
1225
1228
  logger.warning(f"Failed to get regions from AWS API: {e}")
1226
-
1229
+
1227
1230
  # Fallback to well-known regions if API unavailable
1228
1231
  fallback_regions = [
1229
- 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
1230
- 'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3',
1231
- 'ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2',
1232
- 'ca-central-1', 'sa-east-1'
1232
+ "us-east-1",
1233
+ "us-east-2",
1234
+ "us-west-1",
1235
+ "us-west-2",
1236
+ "eu-central-1",
1237
+ "eu-west-1",
1238
+ "eu-west-2",
1239
+ "eu-west-3",
1240
+ "ap-northeast-1",
1241
+ "ap-northeast-2",
1242
+ "ap-southeast-1",
1243
+ "ap-southeast-2",
1244
+ "ca-central-1",
1245
+ "sa-east-1",
1233
1246
  ]
1234
1247
  logger.info(f"Using fallback regions: {len(fallback_regions)} regions")
1235
1248
  return fallback_regions
@@ -1239,11 +1252,11 @@ class DynamicAWSPricing:
1239
1252
  with self._cache_lock:
1240
1253
  cleared_count = len(self._pricing_cache)
1241
1254
  self._pricing_cache.clear()
1242
-
1255
+
1243
1256
  with self._region_cache_lock:
1244
1257
  regional_cleared = len(self._regional_pricing_cache)
1245
1258
  self._regional_pricing_cache.clear()
1246
-
1259
+
1247
1260
  logger.info(f"Cleared {cleared_count} pricing cache entries and {regional_cleared} regional cache entries")
1248
1261
 
1249
1262
 
@@ -1252,36 +1265,34 @@ _pricing_engine = None
1252
1265
  _pricing_lock = threading.Lock()
1253
1266
 
1254
1267
 
1255
- def get_aws_pricing_engine(cache_ttl_hours: int = 24, enable_fallback: bool = True, profile: Optional[str] = None) -> DynamicAWSPricing:
1268
+ def get_aws_pricing_engine(
1269
+ cache_ttl_hours: int = 24, enable_fallback: bool = True, profile: Optional[str] = None
1270
+ ) -> DynamicAWSPricing:
1256
1271
  """
1257
1272
  Get AWS pricing engine instance with enterprise profile integration.
1258
-
1273
+
1259
1274
  Args:
1260
1275
  cache_ttl_hours: Cache time-to-live in hours
1261
1276
  enable_fallback: Enable fallback to estimated pricing
1262
1277
  profile: AWS profile for pricing operations (enterprise integration)
1263
-
1278
+
1264
1279
  Returns:
1265
1280
  DynamicAWSPricing instance
1266
1281
  """
1267
1282
  # Create instance per profile for enterprise multi-profile support
1268
1283
  # This ensures profile isolation and prevents cross-profile cache contamination
1269
- return DynamicAWSPricing(
1270
- cache_ttl_hours=cache_ttl_hours,
1271
- enable_fallback=enable_fallback,
1272
- profile=profile
1273
- )
1284
+ return DynamicAWSPricing(cache_ttl_hours=cache_ttl_hours, enable_fallback=enable_fallback, profile=profile)
1274
1285
 
1275
1286
 
1276
1287
  def get_service_monthly_cost(service_key: str, region: str = "us-east-1", profile: Optional[str] = None) -> float:
1277
1288
  """
1278
1289
  Convenience function to get monthly cost for AWS service with profile support.
1279
-
1290
+
1280
1291
  Args:
1281
1292
  service_key: Service identifier
1282
1293
  region: AWS region
1283
1294
  profile: AWS profile for enterprise --profile compatibility
1284
-
1295
+
1285
1296
  Returns:
1286
1297
  Monthly cost in USD
1287
1298
  """
@@ -1293,33 +1304,35 @@ def get_service_monthly_cost(service_key: str, region: str = "us-east-1", profil
1293
1304
  def calculate_annual_cost(monthly_cost: float) -> float:
1294
1305
  """
1295
1306
  Calculate annual cost from monthly cost.
1296
-
1307
+
1297
1308
  Args:
1298
1309
  monthly_cost: Monthly cost in USD
1299
-
1310
+
1300
1311
  Returns:
1301
1312
  Annual cost in USD
1302
1313
  """
1303
1314
  return monthly_cost * 12
1304
1315
 
1305
1316
 
1306
- def calculate_regional_cost(base_cost: float, region: str, service_key: str = "nat_gateway", profile: Optional[str] = None) -> float:
1317
+ def calculate_regional_cost(
1318
+ base_cost: float, region: str, service_key: str = "nat_gateway", profile: Optional[str] = None
1319
+ ) -> float:
1307
1320
  """
1308
1321
  Apply dynamic regional pricing multiplier to base cost using AWS Pricing API.
1309
-
1322
+
1310
1323
  Args:
1311
1324
  base_cost: Base cost in USD
1312
1325
  region: AWS region
1313
1326
  service_key: Service type for regional multiplier calculation
1314
1327
  profile: AWS profile for enterprise --profile compatibility
1315
-
1328
+
1316
1329
  Returns:
1317
1330
  Region-adjusted cost in USD
1318
1331
  """
1319
1332
  if region == "us-east-1":
1320
1333
  # Base region - no multiplier needed
1321
1334
  return base_cost
1322
-
1335
+
1323
1336
  pricing_engine = get_aws_pricing_engine(profile=profile)
1324
1337
  multiplier = pricing_engine.get_regional_pricing_multiplier(service_key, region, "us-east-1")
1325
1338
  return base_cost * multiplier
@@ -1328,12 +1341,12 @@ def calculate_regional_cost(base_cost: float, region: str, service_key: str = "n
1328
1341
  def get_ec2_monthly_cost(instance_type: str, region: str = "us-east-1", profile: Optional[str] = None) -> float:
1329
1342
  """
1330
1343
  Convenience function to get monthly cost for EC2 instance type with profile support.
1331
-
1344
+
1332
1345
  Args:
1333
1346
  instance_type: EC2 instance type (e.g., t3.micro)
1334
1347
  region: AWS region
1335
1348
  profile: AWS profile for enterprise --profile compatibility
1336
-
1349
+
1337
1350
  Returns:
1338
1351
  Monthly cost in USD
1339
1352
  """
@@ -1342,26 +1355,28 @@ def get_ec2_monthly_cost(instance_type: str, region: str = "us-east-1", profile:
1342
1355
  return result.monthly_cost
1343
1356
 
1344
1357
 
1345
- def calculate_ec2_cost_impact(instance_type: str, count: int = 1, region: str = "us-east-1", profile: Optional[str] = None) -> Dict[str, float]:
1358
+ def calculate_ec2_cost_impact(
1359
+ instance_type: str, count: int = 1, region: str = "us-east-1", profile: Optional[str] = None
1360
+ ) -> Dict[str, float]:
1346
1361
  """
1347
1362
  Calculate cost impact for multiple EC2 instances with profile support.
1348
-
1363
+
1349
1364
  Args:
1350
1365
  instance_type: EC2 instance type
1351
1366
  count: Number of instances
1352
1367
  region: AWS region
1353
1368
  profile: AWS profile for enterprise --profile compatibility
1354
-
1369
+
1355
1370
  Returns:
1356
1371
  Dictionary with cost calculations
1357
1372
  """
1358
1373
  monthly_cost_per_instance = get_ec2_monthly_cost(instance_type, region, profile)
1359
-
1374
+
1360
1375
  return {
1361
1376
  "monthly_cost_per_instance": monthly_cost_per_instance,
1362
1377
  "total_monthly_cost": monthly_cost_per_instance * count,
1363
1378
  "total_annual_cost": monthly_cost_per_instance * count * 12,
1364
- "instance_count": count
1379
+ "instance_count": count,
1365
1380
  }
1366
1381
 
1367
1382
 
@@ -1369,6 +1384,7 @@ def calculate_ec2_cost_impact(instance_type: str, count: int = 1, region: str =
1369
1384
  # ENTERPRISE CONVENIENCE FUNCTIONS - Strategic Requirements Integration
1370
1385
  # ============================================================================
1371
1386
 
1387
+
1372
1388
  def get_ec2_instance_hourly_cost(instance_type: str, region: str = "us-east-1", profile: Optional[str] = None) -> float:
1373
1389
  """Enterprise convenience function for EC2 hourly cost (Strategic Requirement #1)."""
1374
1390
  pricing_engine = get_aws_pricing_engine(profile=profile)
@@ -1387,73 +1403,85 @@ def get_nat_gateway_monthly_cost(region: str = "us-east-1", profile: Optional[st
1387
1403
  return pricing_engine.get_nat_gateway_monthly_cost(region)
1388
1404
 
1389
1405
 
1390
- def get_ebs_gb_monthly_cost(volume_type: str = "gp3", region: str = "us-east-1", profile: Optional[str] = None) -> float:
1406
+ def get_ebs_gb_monthly_cost(
1407
+ volume_type: str = "gp3", region: str = "us-east-1", profile: Optional[str] = None
1408
+ ) -> float:
1391
1409
  """Enterprise convenience function for EBS per-GB monthly cost (Strategic Requirement #4)."""
1392
1410
  pricing_engine = get_aws_pricing_engine(profile=profile)
1393
1411
  return pricing_engine.get_ebs_gb_monthly_cost(volume_type, region)
1394
1412
 
1395
1413
 
1396
- def get_multi_service_cost_analysis(regions: List[str], services: Optional[List[str]] = None, profile: Optional[str] = None) -> Dict[str, Dict[str, float]]:
1414
+ def get_multi_service_cost_analysis(
1415
+ regions: List[str], services: Optional[List[str]] = None, profile: Optional[str] = None
1416
+ ) -> Dict[str, Dict[str, float]]:
1397
1417
  """
1398
1418
  Enterprise function for multi-region, multi-service cost analysis with <1s performance.
1399
-
1419
+
1400
1420
  Args:
1401
1421
  regions: List of AWS regions to analyze
1402
1422
  services: List of service keys (default: common enterprise services)
1403
1423
  profile: AWS profile for enterprise --profile compatibility
1404
-
1424
+
1405
1425
  Returns:
1406
1426
  Dictionary mapping region to service costs
1407
1427
  """
1408
1428
  if services is None:
1409
1429
  services = ["nat_gateway", "elastic_ip", "ebs_gp3", "vpc_endpoint", "loadbalancer_application"]
1410
-
1430
+
1411
1431
  pricing_engine = get_aws_pricing_engine(profile=profile)
1412
1432
  results = {}
1413
-
1433
+
1414
1434
  for region in regions:
1415
1435
  service_requests = [(service, region) for service in services]
1416
1436
  pricing_results = pricing_engine.get_multi_service_pricing(service_requests)
1417
-
1437
+
1418
1438
  results[region] = {
1419
- service: pricing_results.get(f"{service}:{region}", AWSPricingResult(
1420
- service_key=service, region=region, monthly_cost=0.0,
1421
- pricing_source="error", last_updated=datetime.now()
1422
- )).monthly_cost
1439
+ service: pricing_results.get(
1440
+ f"{service}:{region}",
1441
+ AWSPricingResult(
1442
+ service_key=service,
1443
+ region=region,
1444
+ monthly_cost=0.0,
1445
+ pricing_source="error",
1446
+ last_updated=datetime.now(),
1447
+ ),
1448
+ ).monthly_cost
1423
1449
  for service in services
1424
1450
  }
1425
-
1451
+
1426
1452
  return results
1427
1453
 
1428
1454
 
1429
1455
  def warm_pricing_cache_for_enterprise(regions: List[str], profile: Optional[str] = None) -> None:
1430
1456
  """
1431
1457
  Enterprise cache warming for optimal <1s response times across regions.
1432
-
1458
+
1433
1459
  Args:
1434
1460
  regions: List of AWS regions to warm cache for
1435
1461
  profile: AWS profile for enterprise --profile compatibility
1436
1462
  """
1437
1463
  pricing_engine = get_aws_pricing_engine(profile=profile)
1438
-
1464
+
1439
1465
  console.print(f"[dim]Warming enterprise pricing cache for {len(regions)} regions...[/]")
1440
-
1466
+
1441
1467
  for region in regions:
1442
1468
  pricing_engine.warm_cache_for_region(region)
1443
-
1469
+
1444
1470
  console.print("[dim]Enterprise pricing cache warming completed[/]")
1445
1471
 
1446
1472
 
1447
- def get_regional_pricing_multiplier(service_key: str, target_region: str, base_region: str = "us-east-1", profile: Optional[str] = None) -> float:
1473
+ def get_regional_pricing_multiplier(
1474
+ service_key: str, target_region: str, base_region: str = "us-east-1", profile: Optional[str] = None
1475
+ ) -> float:
1448
1476
  """
1449
1477
  Get dynamic regional pricing multiplier using AWS Pricing API.
1450
-
1478
+
1451
1479
  Args:
1452
1480
  service_key: Service identifier (nat_gateway, elastic_ip, etc.)
1453
1481
  target_region: Target region to get multiplier for
1454
1482
  base_region: Base region for comparison (default us-east-1)
1455
1483
  profile: AWS profile for enterprise --profile compatibility
1456
-
1484
+
1457
1485
  Returns:
1458
1486
  Regional pricing multiplier (target_price / base_price)
1459
1487
  """
@@ -1464,58 +1492,53 @@ def get_regional_pricing_multiplier(service_key: str, target_region: str, base_r
1464
1492
  def get_all_regions_pricing(service_key: str, profile: Optional[str] = None) -> Dict[str, float]:
1465
1493
  """
1466
1494
  Get pricing for a service across all AWS regions dynamically.
1467
-
1495
+
1468
1496
  Args:
1469
1497
  service_key: Service identifier
1470
1498
  profile: AWS profile for enterprise --profile compatibility
1471
-
1499
+
1472
1500
  Returns:
1473
1501
  Dictionary mapping region to monthly cost
1474
1502
  """
1475
1503
  pricing_engine = get_aws_pricing_engine(profile=profile)
1476
1504
  regions = pricing_engine.get_available_regions()
1477
-
1505
+
1478
1506
  results = {}
1479
1507
  service_requests = [(service_key, region) for region in regions]
1480
1508
  pricing_results = pricing_engine.get_multi_service_pricing(service_requests)
1481
-
1509
+
1482
1510
  for region in regions:
1483
1511
  key = f"{service_key}:{region}"
1484
1512
  if key in pricing_results:
1485
1513
  results[region] = pricing_results[key].monthly_cost
1486
1514
  else:
1487
1515
  results[region] = 0.0
1488
-
1516
+
1489
1517
  return results
1490
1518
 
1491
1519
 
1492
1520
  # Export main functions
1493
1521
  __all__ = [
1494
1522
  # Core Classes
1495
- 'DynamicAWSPricing',
1496
- 'AWSPricingResult',
1497
-
1523
+ "DynamicAWSPricing",
1524
+ "AWSPricingResult",
1498
1525
  # Core Factory Functions
1499
- 'get_aws_pricing_engine',
1500
-
1526
+ "get_aws_pricing_engine",
1501
1527
  # General Service Functions
1502
- 'get_service_monthly_cost',
1503
- 'get_ec2_monthly_cost',
1504
- 'calculate_ec2_cost_impact',
1505
- 'calculate_annual_cost',
1506
- 'calculate_regional_cost',
1507
-
1528
+ "get_service_monthly_cost",
1529
+ "get_ec2_monthly_cost",
1530
+ "calculate_ec2_cost_impact",
1531
+ "calculate_annual_cost",
1532
+ "calculate_regional_cost",
1508
1533
  # Strategic Requirements - Enterprise Service Methods
1509
- 'get_ec2_instance_hourly_cost', # Strategic Requirement #1
1510
- 'get_eip_monthly_cost', # Strategic Requirement #2
1511
- 'get_nat_gateway_monthly_cost', # Strategic Requirement #3
1512
- 'get_ebs_gb_monthly_cost', # Strategic Requirement #4
1513
-
1534
+ "get_ec2_instance_hourly_cost", # Strategic Requirement #1
1535
+ "get_eip_monthly_cost", # Strategic Requirement #2
1536
+ "get_nat_gateway_monthly_cost", # Strategic Requirement #3
1537
+ "get_ebs_gb_monthly_cost", # Strategic Requirement #4
1514
1538
  # Enterprise Performance Functions
1515
- 'get_multi_service_cost_analysis',
1516
- 'warm_pricing_cache_for_enterprise',
1517
-
1539
+ "get_multi_service_cost_analysis",
1540
+ "warm_pricing_cache_for_enterprise",
1518
1541
  # Dynamic Regional Pricing Functions
1519
- 'get_regional_pricing_multiplier',
1520
- 'get_all_regions_pricing'
1521
- ]
1542
+ "get_regional_pricing_multiplier",
1543
+ "get_all_regions_pricing",
1544
+ ]