costfluent 1.0.0__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.
- costfluent/__init__.py +32 -0
- costfluent/_contract.py +27 -0
- costfluent/_sdk.py +392 -0
- costfluent/_version.py +1 -0
- costfluent/api/__init__.py +1 -0
- costfluent/api/allocation/__init__.py +1 -0
- costfluent/api/allocation/create_allocation_rule.py +239 -0
- costfluent/api/allocation/create_segment.py +239 -0
- costfluent/api/allocation/delete_allocation_rule.py +248 -0
- costfluent/api/allocation/delete_segment.py +257 -0
- costfluent/api/allocation/get_allocation_coverage.py +257 -0
- costfluent/api/allocation/list_allocation_rules.py +250 -0
- costfluent/api/allocation/list_segments.py +244 -0
- costfluent/api/allocation/preview_allocation_rule.py +238 -0
- costfluent/api/allocation/reorder_allocation_rules.py +219 -0
- costfluent/api/allocation/update_allocation_rule.py +270 -0
- costfluent/api/allocation/update_segment.py +283 -0
- costfluent/api/anomalies/__init__.py +1 -0
- costfluent/api/anomalies/acknowledge_anomaly.py +200 -0
- costfluent/api/anomalies/list_anomalies.py +329 -0
- costfluent/api/apps/__init__.py +1 -0
- costfluent/api/apps/create_app.py +259 -0
- costfluent/api/apps/delete_app.py +231 -0
- costfluent/api/apps/get_app.py +202 -0
- costfluent/api/apps/list_apps.py +227 -0
- costfluent/api/apps/update_app.py +270 -0
- costfluent/api/audit_logs/__init__.py +1 -0
- costfluent/api/audit_logs/list_audit_logs.py +356 -0
- costfluent/api/billing/__init__.py +1 -0
- costfluent/api/billing/get_entitlement.py +179 -0
- costfluent/api/billing/get_usage.py +179 -0
- costfluent/api/budgets/__init__.py +1 -0
- costfluent/api/budgets/create_budget.py +259 -0
- costfluent/api/budgets/delete_budget.py +223 -0
- costfluent/api/budgets/get_budget.py +202 -0
- costfluent/api/budgets/list_budgets.py +249 -0
- costfluent/api/budgets/update_budget.py +270 -0
- costfluent/api/business_metrics/__init__.py +1 -0
- costfluent/api/business_metrics/create_business_metric.py +251 -0
- costfluent/api/business_metrics/delete_business_metric.py +248 -0
- costfluent/api/business_metrics/get_business_metric.py +228 -0
- costfluent/api/business_metrics/import_business_metric_values.py +277 -0
- costfluent/api/business_metrics/list_business_metric_values.py +307 -0
- costfluent/api/business_metrics/list_business_metrics.py +246 -0
- costfluent/api/business_metrics/update_business_metric.py +278 -0
- costfluent/api/contracting/__init__.py +1 -0
- costfluent/api/contracting/get_data_processing_agreement.py +183 -0
- costfluent/api/cost_alerts/__init__.py +1 -0
- costfluent/api/cost_alerts/create_cost_alert.py +267 -0
- costfluent/api/cost_alerts/delete_cost_alert.py +248 -0
- costfluent/api/cost_alerts/get_cost_alert.py +228 -0
- costfluent/api/cost_alerts/get_cost_alert_events.py +262 -0
- costfluent/api/cost_alerts/list_cost_alerts.py +244 -0
- costfluent/api/cost_alerts/pause_cost_alert.py +248 -0
- costfluent/api/cost_alerts/resume_cost_alert.py +248 -0
- costfluent/api/cost_alerts/update_cost_alert.py +298 -0
- costfluent/api/cost_data/__init__.py +1 -0
- costfluent/api/cost_data/get_cost_data.py +491 -0
- costfluent/api/cost_data/get_cost_forecast.py +232 -0
- costfluent/api/cost_data/get_cost_summary.py +362 -0
- costfluent/api/cost_filters/__init__.py +1 -0
- costfluent/api/cost_filters/translate_cost_filter.py +224 -0
- costfluent/api/cost_reports/__init__.py +1 -0
- costfluent/api/cost_reports/create_cost_report.py +247 -0
- costfluent/api/cost_reports/delete_cost_report.py +266 -0
- costfluent/api/cost_reports/get_cost_report.py +228 -0
- costfluent/api/cost_reports/get_cost_report_data.py +237 -0
- costfluent/api/cost_reports/list_cost_reports.py +264 -0
- costfluent/api/cost_reports/update_cost_report.py +278 -0
- costfluent/api/custom_provider/__init__.py +1 -0
- costfluent/api/custom_provider/delete_custom_cost_upload.py +218 -0
- costfluent/api/custom_provider/get_custom_cost_upload.py +216 -0
- costfluent/api/custom_provider/list_custom_cost_uploads.py +247 -0
- costfluent/api/custom_provider/submit_costs.py +291 -0
- costfluent/api/custom_provider/upload_custom_costs.py +271 -0
- costfluent/api/dashboards/__init__.py +1 -0
- costfluent/api/dashboards/create_dashboard.py +247 -0
- costfluent/api/dashboards/create_dashboard_widget.py +287 -0
- costfluent/api/dashboards/delete_dashboard.py +248 -0
- costfluent/api/dashboards/delete_dashboard_widget.py +275 -0
- costfluent/api/dashboards/get_dashboard.py +228 -0
- costfluent/api/dashboards/get_dashboard_widget.py +242 -0
- costfluent/api/dashboards/list_dashboards.py +244 -0
- costfluent/api/dashboards/set_dashboard_default.py +266 -0
- costfluent/api/dashboards/set_dashboard_saved_filter.py +262 -0
- costfluent/api/dashboards/update_dashboard.py +278 -0
- costfluent/api/dashboards/update_dashboard_widget.py +305 -0
- costfluent/api/exchange_rates/__init__.py +1 -0
- costfluent/api/exchange_rates/get_available_currencies.py +227 -0
- costfluent/api/exchange_rates/get_exchange_rate.py +258 -0
- costfluent/api/exchange_rates/get_fx_policy.py +206 -0
- costfluent/api/exchange_rates/list_exchange_rates.py +320 -0
- costfluent/api/folders/__init__.py +1 -0
- costfluent/api/folders/create_folder.py +247 -0
- costfluent/api/folders/delete_folder.py +252 -0
- costfluent/api/folders/get_folder.py +228 -0
- costfluent/api/folders/list_folders.py +244 -0
- costfluent/api/folders/set_folder_saved_filter.py +266 -0
- costfluent/api/folders/update_folder.py +262 -0
- costfluent/api/kubernetes/__init__.py +1 -0
- costfluent/api/kubernetes/report_kubernetes_usage.py +264 -0
- costfluent/api/providers/__init__.py +1 -0
- costfluent/api/providers/connect_provider.py +247 -0
- costfluent/api/providers/delete_provider.py +240 -0
- costfluent/api/providers/get_available_providers.py +231 -0
- costfluent/api/providers/get_aws_connector.py +179 -0
- costfluent/api/providers/get_provider.py +202 -0
- costfluent/api/providers/get_provider_sync_history.py +251 -0
- costfluent/api/providers/list_providers.py +249 -0
- costfluent/api/providers/provision_gcp_service_account.py +179 -0
- costfluent/api/providers/test_provider_connection.py +219 -0
- costfluent/api/providers/trigger_provider_sync.py +243 -0
- costfluent/api/providers/update_provider.py +275 -0
- costfluent/api/report_notifications/__init__.py +1 -0
- costfluent/api/report_notifications/activate_cost_report_subscription.py +248 -0
- costfluent/api/report_notifications/create_cost_report_subscription.py +251 -0
- costfluent/api/report_notifications/deactivate_cost_report_subscription.py +248 -0
- costfluent/api/report_notifications/delete_cost_report_subscription.py +248 -0
- costfluent/api/report_notifications/get_cost_report_subscription.py +228 -0
- costfluent/api/report_notifications/list_cost_report_subscriptions.py +266 -0
- costfluent/api/report_notifications/update_cost_report_subscription.py +260 -0
- costfluent/api/saved_filters/__init__.py +1 -0
- costfluent/api/saved_filters/create_saved_filter.py +247 -0
- costfluent/api/saved_filters/delete_saved_filter.py +248 -0
- costfluent/api/saved_filters/get_saved_filter.py +228 -0
- costfluent/api/saved_filters/list_saved_filters.py +244 -0
- costfluent/api/saved_filters/update_saved_filter.py +278 -0
- costfluent/api/tags/__init__.py +1 -0
- costfluent/api/tags/delete_tag_key.py +200 -0
- costfluent/api/tags/get_tag_key.py +202 -0
- costfluent/api/tags/get_tag_policy.py +210 -0
- costfluent/api/tags/list_tag_keys.py +249 -0
- costfluent/api/tags/update_tag_policy.py +250 -0
- costfluent/api/teams/__init__.py +1 -0
- costfluent/api/teams/add_team_member.py +236 -0
- costfluent/api/teams/create_team.py +247 -0
- costfluent/api/teams/delete_team.py +223 -0
- costfluent/api/teams/get_team.py +206 -0
- costfluent/api/teams/list_team_workspace_access.py +251 -0
- costfluent/api/teams/list_teams.py +229 -0
- costfluent/api/teams/remove_team_member.py +214 -0
- costfluent/api/teams/remove_team_workspace_access.py +214 -0
- costfluent/api/teams/set_team_workspace_access.py +250 -0
- costfluent/api/teams/update_team.py +258 -0
- costfluent/api/teams/update_team_member.py +250 -0
- costfluent/api/users/__init__.py +1 -0
- costfluent/api/users/get_user.py +202 -0
- costfluent/api/users/invite_user.py +259 -0
- costfluent/api/users/list_users.py +249 -0
- costfluent/api/utility/__init__.py +1 -0
- costfluent/api/utility/me.py +175 -0
- costfluent/api/utility/ping.py +165 -0
- costfluent/api/virtual_tags/__init__.py +1 -0
- costfluent/api/virtual_tags/create_virtual_tag.py +247 -0
- costfluent/api/virtual_tags/delete_virtual_tag.py +257 -0
- costfluent/api/virtual_tags/get_virtual_tag.py +228 -0
- costfluent/api/virtual_tags/list_virtual_tags.py +248 -0
- costfluent/api/virtual_tags/update_virtual_tag.py +258 -0
- costfluent/api/workspace_providers/__init__.py +1 -0
- costfluent/api/workspace_providers/create_workspace_provider.py +255 -0
- costfluent/api/workspace_providers/delete_workspace_provider.py +230 -0
- costfluent/api/workspace_providers/get_workspace_provider.py +228 -0
- costfluent/api/workspace_providers/list_workspace_providers.py +246 -0
- costfluent/api/workspace_providers/update_workspace_provider.py +254 -0
- costfluent/api/workspaces/__init__.py +1 -0
- costfluent/api/workspaces/create_workspace.py +247 -0
- costfluent/api/workspaces/delete_workspace.py +227 -0
- costfluent/api/workspaces/get_workspace.py +202 -0
- costfluent/api/workspaces/list_workspaces.py +229 -0
- costfluent/api/workspaces/update_workspace.py +258 -0
- costfluent/client.py +280 -0
- costfluent/errors.py +16 -0
- costfluent/models/__init__.py +671 -0
- costfluent/models/acknowledge_anomaly_request.py +23 -0
- costfluent/models/activate_cost_report_subscription_request.py +23 -0
- costfluent/models/add_team_member_request.py +50 -0
- costfluent/models/allocation_coverage_currency.py +160 -0
- costfluent/models/allocation_coverage_pool.py +112 -0
- costfluent/models/allocation_coverage_response.py +81 -0
- costfluent/models/allocation_coverage_segment.py +90 -0
- costfluent/models/allocation_coverage_share.py +50 -0
- costfluent/models/allocation_dry_run_response.py +102 -0
- costfluent/models/allocation_dry_run_segment_change_response.py +74 -0
- costfluent/models/allocation_rule_response.py +171 -0
- costfluent/models/anomaly_response.py +247 -0
- costfluent/models/api_error.py +183 -0
- costfluent/models/api_error_entry.py +110 -0
- costfluent/models/api_error_entry_params_type_0.py +45 -0
- costfluent/models/app_response.py +111 -0
- costfluent/models/app_response_config.py +45 -0
- costfluent/models/audit_log_entry_response.py +232 -0
- costfluent/models/aws_connector_response.py +80 -0
- costfluent/models/budget_alert_request.py +42 -0
- costfluent/models/budget_alert_response.py +91 -0
- costfluent/models/budget_interval_unit_value.py +11 -0
- costfluent/models/budget_measure_value.py +9 -0
- costfluent/models/budget_period_request.py +75 -0
- costfluent/models/budget_period_response.py +59 -0
- costfluent/models/budget_response.py +435 -0
- costfluent/models/business_metric_import_response.py +58 -0
- costfluent/models/business_metric_request.py +23 -0
- costfluent/models/business_metric_response.py +107 -0
- costfluent/models/business_metric_value_request.py +105 -0
- costfluent/models/business_metric_value_request_labels_type_0.py +45 -0
- costfluent/models/business_metric_value_response.py +69 -0
- costfluent/models/business_metric_value_response_labels.py +45 -0
- costfluent/models/chart_type.py +13 -0
- costfluent/models/comparison_period.py +11 -0
- costfluent/models/connect_provider_request.py +193 -0
- costfluent/models/connect_provider_request_credentials.py +45 -0
- costfluent/models/connect_provider_request_settings_type_0.py +45 -0
- costfluent/models/connection_test_response.py +110 -0
- costfluent/models/connection_test_response_metadata_type_0.py +45 -0
- costfluent/models/cost_alert_created_response.py +50 -0
- costfluent/models/cost_alert_event_response.py +212 -0
- costfluent/models/cost_alert_response.py +252 -0
- costfluent/models/cost_data_meta.py +199 -0
- costfluent/models/cost_data_point.py +131 -0
- costfluent/models/cost_data_point_dimensions.py +45 -0
- costfluent/models/cost_data_response.py +76 -0
- costfluent/models/cost_entry.py +545 -0
- costfluent/models/cost_entry_tags_type_0.py +45 -0
- costfluent/models/cost_filter_translation_response.py +74 -0
- costfluent/models/cost_forecast_response.py +221 -0
- costfluent/models/cost_group_dimension.py +30 -0
- costfluent/models/cost_report_created_response.py +50 -0
- costfluent/models/cost_report_data_response.py +230 -0
- costfluent/models/cost_report_response.py +255 -0
- costfluent/models/cost_report_settings_request.py +324 -0
- costfluent/models/cost_report_settings_response.py +207 -0
- costfluent/models/cost_report_subscription_created_response.py +50 -0
- costfluent/models/cost_report_subscription_response.py +199 -0
- costfluent/models/cost_summary_response.py +301 -0
- costfluent/models/cost_validation_error.py +58 -0
- costfluent/models/create_allocation_rule_request.py +86 -0
- costfluent/models/create_app_request.py +64 -0
- costfluent/models/create_app_request_config.py +45 -0
- costfluent/models/create_budget_request.py +335 -0
- costfluent/models/create_business_metric_request.py +91 -0
- costfluent/models/create_cost_alert_request.py +210 -0
- costfluent/models/create_cost_report_request.py +291 -0
- costfluent/models/create_cost_report_subscription_request.py +116 -0
- costfluent/models/create_dashboard_request.py +140 -0
- costfluent/models/create_dashboard_widget_request.py +127 -0
- costfluent/models/create_folder_request.py +72 -0
- costfluent/models/create_saved_filter_request.py +69 -0
- costfluent/models/create_segment_request.py +169 -0
- costfluent/models/create_team_request.py +42 -0
- costfluent/models/create_virtual_tag_request.py +130 -0
- costfluent/models/create_workspace_provider_request.py +83 -0
- costfluent/models/create_workspace_request.py +72 -0
- costfluent/models/currency_amount.py +50 -0
- costfluent/models/currency_conversion.py +82 -0
- costfluent/models/currency_presentation.py +92 -0
- costfluent/models/custom_cost_upload_error.py +58 -0
- costfluent/models/custom_cost_upload_request.py +23 -0
- costfluent/models/custom_cost_upload_response.py +282 -0
- costfluent/models/custom_cost_upload_response_billed_totals_type_0.py +45 -0
- costfluent/models/dashboard_created_response.py +50 -0
- costfluent/models/dashboard_response.py +187 -0
- costfluent/models/dashboard_widget_created_response.py +50 -0
- costfluent/models/deactivate_cost_report_subscription_request.py +23 -0
- costfluent/models/delete_allocation_rule_request.py +23 -0
- costfluent/models/delete_app_request.py +23 -0
- costfluent/models/delete_budget_request.py +23 -0
- costfluent/models/delete_cost_alert_request.py +23 -0
- costfluent/models/delete_cost_report_request.py +23 -0
- costfluent/models/delete_cost_report_subscription_request.py +23 -0
- costfluent/models/delete_dashboard_request.py +23 -0
- costfluent/models/delete_dashboard_widget_request.py +23 -0
- costfluent/models/delete_folder_request.py +23 -0
- costfluent/models/delete_provider_request.py +23 -0
- costfluent/models/delete_saved_filter_request.py +23 -0
- costfluent/models/delete_segment_request.py +23 -0
- costfluent/models/delete_tag_key_request.py +23 -0
- costfluent/models/delete_team_request.py +23 -0
- costfluent/models/delete_virtual_tag_request.py +23 -0
- costfluent/models/delete_workspace_provider_request.py +23 -0
- costfluent/models/delete_workspace_request.py +23 -0
- costfluent/models/dpa_execution_response.py +116 -0
- costfluent/models/dpa_status_response.py +113 -0
- costfluent/models/entitlement_response.py +303 -0
- costfluent/models/exchange_rate_response.py +75 -0
- costfluent/models/folder_created_response.py +50 -0
- costfluent/models/folder_response.py +141 -0
- costfluent/models/forecast_month_response.py +66 -0
- costfluent/models/fx_policy_response.py +126 -0
- costfluent/models/fx_policy_status_response.py +91 -0
- costfluent/models/gcp_service_account_response.py +86 -0
- costfluent/models/get_allocation_coverage_request.py +23 -0
- costfluent/models/get_app_request.py +23 -0
- costfluent/models/get_budget_request.py +23 -0
- costfluent/models/get_cost_alert_events_request.py +45 -0
- costfluent/models/get_cost_alert_request.py +23 -0
- costfluent/models/get_cost_data_request.py +45 -0
- costfluent/models/get_cost_forecast_request.py +23 -0
- costfluent/models/get_cost_report_data_request.py +23 -0
- costfluent/models/get_cost_report_request.py +23 -0
- costfluent/models/get_cost_report_subscription_request.py +23 -0
- costfluent/models/get_cost_summary_request.py +23 -0
- costfluent/models/get_dashboard_request.py +23 -0
- costfluent/models/get_dashboard_widget_request.py +23 -0
- costfluent/models/get_exchange_rate_request.py +23 -0
- costfluent/models/get_folder_request.py +23 -0
- costfluent/models/get_fx_policy_request.py +23 -0
- costfluent/models/get_provider_request.py +23 -0
- costfluent/models/get_provider_sync_history_request.py +45 -0
- costfluent/models/get_saved_filter_request.py +23 -0
- costfluent/models/get_tag_key_request.py +23 -0
- costfluent/models/get_tag_policy_request.py +23 -0
- costfluent/models/get_team_request.py +23 -0
- costfluent/models/get_user_request.py +23 -0
- costfluent/models/get_virtual_tag_request.py +23 -0
- costfluent/models/get_workspace_provider_request.py +23 -0
- costfluent/models/get_workspace_request.py +23 -0
- costfluent/models/import_business_metric_values_request.py +83 -0
- costfluent/models/invitation_role.py +10 -0
- costfluent/models/invite_user_request.py +68 -0
- costfluent/models/kubernetes_report.py +152 -0
- costfluent/models/kubernetes_report_accepted_response.py +58 -0
- costfluent/models/kubernetes_report_container.py +132 -0
- costfluent/models/kubernetes_report_node.py +274 -0
- costfluent/models/kubernetes_report_node_annotations_type_0.py +45 -0
- costfluent/models/kubernetes_report_node_labels_type_0.py +45 -0
- costfluent/models/kubernetes_report_pod.py +255 -0
- costfluent/models/kubernetes_report_pod_annotations_type_0.py +45 -0
- costfluent/models/kubernetes_report_pod_labels_type_0.py +45 -0
- costfluent/models/kubernetes_report_pod_namespace_labels_type_0.py +45 -0
- costfluent/models/list_allocation_rules_request.py +45 -0
- costfluent/models/list_anomalies_request.py +45 -0
- costfluent/models/list_apps_request.py +45 -0
- costfluent/models/list_audit_logs_request.py +45 -0
- costfluent/models/list_budgets_request.py +45 -0
- costfluent/models/list_business_metric_values_request.py +45 -0
- costfluent/models/list_business_metrics_request.py +45 -0
- costfluent/models/list_cost_alerts_request.py +45 -0
- costfluent/models/list_cost_report_subscriptions_request.py +45 -0
- costfluent/models/list_cost_reports_request.py +45 -0
- costfluent/models/list_custom_cost_uploads_request.py +45 -0
- costfluent/models/list_dashboards_request.py +45 -0
- costfluent/models/list_exchange_rates_request.py +45 -0
- costfluent/models/list_folders_request.py +45 -0
- costfluent/models/list_providers_request.py +45 -0
- costfluent/models/list_saved_filters_request.py +45 -0
- costfluent/models/list_segments_request.py +45 -0
- costfluent/models/list_tag_keys_request.py +45 -0
- costfluent/models/list_team_workspace_access_request.py +45 -0
- costfluent/models/list_teams_request.py +45 -0
- costfluent/models/list_users_request.py +45 -0
- costfluent/models/list_virtual_tags_request.py +45 -0
- costfluent/models/list_workspace_providers_request.py +45 -0
- costfluent/models/me_response.py +130 -0
- costfluent/models/paginated_request.py +23 -0
- costfluent/models/paginated_response_of_allocation_rule_response.py +66 -0
- costfluent/models/paginated_response_of_anomaly_response.py +66 -0
- costfluent/models/paginated_response_of_app_response.py +66 -0
- costfluent/models/paginated_response_of_audit_log_entry_response.py +66 -0
- costfluent/models/paginated_response_of_budget_response.py +66 -0
- costfluent/models/paginated_response_of_business_metric_response.py +66 -0
- costfluent/models/paginated_response_of_business_metric_value_response.py +66 -0
- costfluent/models/paginated_response_of_cost_alert_event_response.py +66 -0
- costfluent/models/paginated_response_of_cost_alert_response.py +66 -0
- costfluent/models/paginated_response_of_cost_report_response.py +66 -0
- costfluent/models/paginated_response_of_cost_report_subscription_response.py +70 -0
- costfluent/models/paginated_response_of_custom_cost_upload_response.py +66 -0
- costfluent/models/paginated_response_of_dashboard_response.py +66 -0
- costfluent/models/paginated_response_of_exchange_rate_response.py +66 -0
- costfluent/models/paginated_response_of_folder_response.py +66 -0
- costfluent/models/paginated_response_of_provider_definition_response.py +66 -0
- costfluent/models/paginated_response_of_provider_response.py +66 -0
- costfluent/models/paginated_response_of_saved_filter_response.py +66 -0
- costfluent/models/paginated_response_of_segment_response.py +66 -0
- costfluent/models/paginated_response_of_string.py +56 -0
- costfluent/models/paginated_response_of_sync_history_entry_response.py +66 -0
- costfluent/models/paginated_response_of_tag_key_response.py +66 -0
- costfluent/models/paginated_response_of_team_response.py +66 -0
- costfluent/models/paginated_response_of_team_workspace_access_response.py +66 -0
- costfluent/models/paginated_response_of_user_response.py +66 -0
- costfluent/models/paginated_response_of_virtual_tag_response.py +66 -0
- costfluent/models/paginated_response_of_workspace_provider_response.py +66 -0
- costfluent/models/paginated_response_of_workspace_response.py +66 -0
- costfluent/models/pagination_links.py +124 -0
- costfluent/models/pause_cost_alert_request.py +23 -0
- costfluent/models/ping_response.py +59 -0
- costfluent/models/plan_ladder_entry_response.py +166 -0
- costfluent/models/preview_allocation_rule_request.py +86 -0
- costfluent/models/provider_cost_breakdown.py +66 -0
- costfluent/models/provider_created_response.py +66 -0
- costfluent/models/provider_definition_response.py +112 -0
- costfluent/models/provider_response.py +283 -0
- costfluent/models/remove_team_member_request.py +23 -0
- costfluent/models/remove_team_workspace_access_request.py +23 -0
- costfluent/models/reorder_allocation_rules_request.py +55 -0
- costfluent/models/report_breakdown_response.py +108 -0
- costfluent/models/report_comparison_point_response.py +59 -0
- costfluent/models/report_comparison_response.py +75 -0
- costfluent/models/report_group_response.py +100 -0
- costfluent/models/report_grouping_request.py +65 -0
- costfluent/models/report_grouping_response.py +64 -0
- costfluent/models/report_summary_response.py +243 -0
- costfluent/models/report_time_series_response.py +65 -0
- costfluent/models/report_unit_cost_point_response.py +85 -0
- costfluent/models/report_unit_cost_response.py +122 -0
- costfluent/models/resume_cost_alert_request.py +23 -0
- costfluent/models/saved_filter_created_response.py +50 -0
- costfluent/models/saved_filter_response.py +75 -0
- costfluent/models/segment_response.py +201 -0
- costfluent/models/segment_split_method_value.py +10 -0
- costfluent/models/segment_split_request.py +77 -0
- costfluent/models/segment_split_response.py +64 -0
- costfluent/models/segment_split_target_request.py +64 -0
- costfluent/models/segment_split_target_response.py +64 -0
- costfluent/models/service_cost_breakdown.py +58 -0
- costfluent/models/set_dashboard_default_request.py +50 -0
- costfluent/models/set_dashboard_saved_filter_request.py +64 -0
- costfluent/models/set_folder_saved_filter_request.py +64 -0
- costfluent/models/set_team_workspace_access_request.py +42 -0
- costfluent/models/submit_costs_request.py +67 -0
- costfluent/models/submit_costs_response.py +115 -0
- costfluent/models/sync_history_entry_response.py +131 -0
- costfluent/models/sync_trigger_response.py +64 -0
- costfluent/models/tag_key_response.py +115 -0
- costfluent/models/tag_policy_response.py +109 -0
- costfluent/models/tag_policy_rule_request.py +75 -0
- costfluent/models/tag_policy_rule_response.py +72 -0
- costfluent/models/team_detail_response.py +109 -0
- costfluent/models/team_member_response.py +83 -0
- costfluent/models/team_response.py +75 -0
- costfluent/models/team_workspace_access_response.py +58 -0
- costfluent/models/test_provider_connection_request.py +23 -0
- costfluent/models/translate_cost_filter_request.py +23 -0
- costfluent/models/trigger_provider_sync_request.py +42 -0
- costfluent/models/unit_cost_label_condition_request.py +50 -0
- costfluent/models/unit_cost_label_condition_response.py +50 -0
- costfluent/models/unit_quantity.py +50 -0
- costfluent/models/update_allocation_rule_request.py +100 -0
- costfluent/models/update_app_request.py +96 -0
- costfluent/models/update_app_request_config_type_0.py +45 -0
- costfluent/models/update_budget_request.py +230 -0
- costfluent/models/update_business_metric_request.py +74 -0
- costfluent/models/update_cost_alert_route_request.py +276 -0
- costfluent/models/update_cost_report_request.py +343 -0
- costfluent/models/update_cost_report_subscription_route_request.py +155 -0
- costfluent/models/update_dashboard_request.py +155 -0
- costfluent/models/update_dashboard_widget_request.py +132 -0
- costfluent/models/update_folder_request.py +93 -0
- costfluent/models/update_provider_request.py +223 -0
- costfluent/models/update_provider_request_credentials_type_0.py +45 -0
- costfluent/models/update_provider_request_settings_type_0.py +45 -0
- costfluent/models/update_saved_filter_request.py +94 -0
- costfluent/models/update_segment_request.py +172 -0
- costfluent/models/update_tag_policy_request.py +56 -0
- costfluent/models/update_team_member_request.py +42 -0
- costfluent/models/update_team_request.py +42 -0
- costfluent/models/update_virtual_tag_route_request.py +200 -0
- costfluent/models/update_workspace_provider_request.py +95 -0
- costfluent/models/update_workspace_request.py +296 -0
- costfluent/models/upload_custom_costs_request.py +88 -0
- costfluent/models/usage_history_entry_response.py +67 -0
- costfluent/models/usage_response.py +203 -0
- costfluent/models/user_invited_response.py +50 -0
- costfluent/models/user_response.py +153 -0
- costfluent/models/virtual_tag_created_response.py +50 -0
- costfluent/models/virtual_tag_response.py +161 -0
- costfluent/models/virtual_tag_rule_request.py +50 -0
- costfluent/models/virtual_tag_rule_response.py +50 -0
- costfluent/models/widget_position_request.py +69 -0
- costfluent/models/widget_position_response.py +66 -0
- costfluent/models/widget_response.py +168 -0
- costfluent/models/widget_response_config_type_0.py +45 -0
- costfluent/models/workspace_created_response.py +50 -0
- costfluent/models/workspace_provider_response.py +113 -0
- costfluent/models/workspace_report_defaults_request.py +196 -0
- costfluent/models/workspace_report_defaults_response.py +98 -0
- costfluent/models/workspace_response.py +205 -0
- costfluent/py.typed +0 -0
- costfluent/types.py +54 -0
- costfluent-1.0.0.dist-info/METADATA +112 -0
- costfluent-1.0.0.dist-info/RECORD +481 -0
- costfluent-1.0.0.dist-info/WHEEL +4 -0
- costfluent-1.0.0.dist-info/licenses/LICENSE +202 -0
costfluent/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Python client for the Costfluent Public API.
|
|
2
|
+
|
|
3
|
+
Every operation is a generated module under ``costfluent.api``, its models under
|
|
4
|
+
``costfluent.models``; ``create_client`` builds the client they take::
|
|
5
|
+
|
|
6
|
+
from costfluent import create_client, paginate
|
|
7
|
+
from costfluent.api.workspaces import list_workspaces
|
|
8
|
+
|
|
9
|
+
client = create_client(token)
|
|
10
|
+
for workspace in paginate(list_workspaces.sync, client=client, limit=100):
|
|
11
|
+
print(workspace.name)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from ._contract import CONTRACT_SHA256
|
|
15
|
+
from ._sdk import DEFAULT_BASE_URL, CostfluentError, apaginate, create_client, paginate
|
|
16
|
+
from ._version import __version__
|
|
17
|
+
from .client import AuthenticatedClient
|
|
18
|
+
from .types import UNSET, Response, Unset
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"CONTRACT_SHA256",
|
|
22
|
+
"DEFAULT_BASE_URL",
|
|
23
|
+
"UNSET",
|
|
24
|
+
"AuthenticatedClient",
|
|
25
|
+
"CostfluentError",
|
|
26
|
+
"Response",
|
|
27
|
+
"Unset",
|
|
28
|
+
"__version__",
|
|
29
|
+
"apaginate",
|
|
30
|
+
"create_client",
|
|
31
|
+
"paginate",
|
|
32
|
+
]
|
costfluent/_contract.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Generated from the Public API document. Do not edit."""
|
|
2
|
+
|
|
3
|
+
CONTRACT_SHA256 = "0b9a6864ae2611a073fa0ae842ea3ef2d93f3ddc716b7c59fbd2de0618cddb5f"
|
|
4
|
+
"""SHA-256 of the Public API document this package was generated from."""
|
|
5
|
+
|
|
6
|
+
IDEMPOTENT_CREATES: tuple[str, ...] = (
|
|
7
|
+
"/v1/allocation/rules",
|
|
8
|
+
"/v1/apps",
|
|
9
|
+
"/v1/budgets",
|
|
10
|
+
"/v1/business-metrics",
|
|
11
|
+
"/v1/cost-alerts",
|
|
12
|
+
"/v1/cost-report-subscriptions",
|
|
13
|
+
"/v1/cost-reports",
|
|
14
|
+
"/v1/custom-providers/{providerId}/costs",
|
|
15
|
+
"/v1/dashboards",
|
|
16
|
+
"/v1/dashboards/{dashboardId}/widgets",
|
|
17
|
+
"/v1/folders",
|
|
18
|
+
"/v1/providers",
|
|
19
|
+
"/v1/saved-filters",
|
|
20
|
+
"/v1/segments",
|
|
21
|
+
"/v1/teams",
|
|
22
|
+
"/v1/users",
|
|
23
|
+
"/v1/virtual-tags",
|
|
24
|
+
"/v1/workspace-providers",
|
|
25
|
+
"/v1/workspaces",
|
|
26
|
+
)
|
|
27
|
+
"""The creates that take an Idempotency-Key; create_client sends a fresh one with each."""
|
costfluent/_sdk.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""The hand-written layer over the generated client: authentication, retries, idempotency keys,
|
|
2
|
+
typed errors and pagination, with the same rules as the Costfluent Go client."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import email.utils
|
|
8
|
+
import itertools
|
|
9
|
+
import random
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping, Sequence
|
|
13
|
+
from datetime import UTC, datetime
|
|
14
|
+
from typing import Any, Protocol, Self, TypeVar
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from ._contract import IDEMPOTENT_CREATES
|
|
19
|
+
from ._version import __version__
|
|
20
|
+
from .client import AuthenticatedClient
|
|
21
|
+
from .models.api_error import ApiError
|
|
22
|
+
from .models.api_error_entry import ApiErrorEntry
|
|
23
|
+
from .types import Unset
|
|
24
|
+
|
|
25
|
+
DEFAULT_BASE_URL = "https://api.costfluent.com"
|
|
26
|
+
DEFAULT_TIMEOUT = 30.0
|
|
27
|
+
USER_AGENT = f"costfluent-python/{__version__}"
|
|
28
|
+
|
|
29
|
+
MAX_RETRIES = 3
|
|
30
|
+
BASE_DELAY = 1.0
|
|
31
|
+
MAX_DELAY = 30.0
|
|
32
|
+
# The longest Retry-After a request waits out. A longer one means the token's hourly quota is spent,
|
|
33
|
+
# and the 429 is raised rather than held for most of an hour.
|
|
34
|
+
MAX_RETRY_WAIT = 60.0
|
|
35
|
+
|
|
36
|
+
IDEMPOTENCY_KEY = "Idempotency-Key"
|
|
37
|
+
PROBLEM_MEDIA_TYPE = "application/problem+json"
|
|
38
|
+
# The name the API files an error under when it names no request field.
|
|
39
|
+
GENERAL_ERRORS_FIELD = "generalErrors"
|
|
40
|
+
|
|
41
|
+
# Seams the tests replace, so a retry never really waits and an HTTP date has a fixed now.
|
|
42
|
+
_sleep: Callable[[float], None] = time.sleep
|
|
43
|
+
_async_sleep: Callable[[float], Awaitable[None]] = asyncio.sleep
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _now() -> datetime:
|
|
47
|
+
return datetime.now(UTC)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _segment_pattern(template: str) -> Callable[[str], bool]:
|
|
51
|
+
segments = template.strip("/").split("/")
|
|
52
|
+
|
|
53
|
+
def matches(path: str) -> bool:
|
|
54
|
+
parts = path.strip("/").split("/")
|
|
55
|
+
# A base URL with a path prefix puts it before the API's own path.
|
|
56
|
+
parts = parts[len(parts) - len(segments) :] if len(parts) >= len(segments) else []
|
|
57
|
+
return bool(parts) and all(
|
|
58
|
+
segment.startswith("{") or segment == part for segment, part in zip(segments, parts, strict=True)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return matches
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_CREATES = tuple(_segment_pattern(template) for template in IDEMPOTENT_CREATES)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CostfluentError(Exception):
|
|
68
|
+
"""An error status from the API, raised by every call once its retries are spent.
|
|
69
|
+
|
|
70
|
+
``problem`` is the API's problem details when the response carried them; a response without
|
|
71
|
+
them (a proxy's plain 404 or 502) leaves it ``None`` and ``code`` empty.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, status: int, problem: ApiError | None = None, request_id: str | None = None) -> None:
|
|
75
|
+
self.status = status
|
|
76
|
+
self.problem = problem
|
|
77
|
+
self.request_id = request_id
|
|
78
|
+
super().__init__(self._message())
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_response(cls, response: httpx.Response) -> Self:
|
|
82
|
+
problem = None
|
|
83
|
+
if _is_problem(response):
|
|
84
|
+
try:
|
|
85
|
+
problem = ApiError.from_dict(response.json())
|
|
86
|
+
except (KeyError, TypeError, ValueError):
|
|
87
|
+
problem = None
|
|
88
|
+
return cls(response.status_code, problem, response.headers.get("X-Request-Id"))
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def code(self) -> str:
|
|
92
|
+
"""The problem's snake_case code, such as ``not_found`` or ``idempotency_key_reused``."""
|
|
93
|
+
return self.problem.code if self.problem else ""
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def detail(self) -> str | None:
|
|
97
|
+
detail = self.problem.detail if self.problem else None
|
|
98
|
+
return detail if isinstance(detail, str) else None
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def retryable(self) -> bool:
|
|
102
|
+
return self.problem.retryable if self.problem else False
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def trace_id(self) -> str | None:
|
|
106
|
+
trace_id = self.problem.trace_id if self.problem else None
|
|
107
|
+
return trace_id if isinstance(trace_id, str) else None
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def field_errors(self) -> list[ApiErrorEntry]:
|
|
111
|
+
errors = self.problem.errors if self.problem else None
|
|
112
|
+
return errors if isinstance(errors, list) else []
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def is_not_found(self) -> bool:
|
|
116
|
+
"""The API found no such object. A proxy's plain 404 says nothing about the object, so it is not one."""
|
|
117
|
+
return self.status == 404 and self.problem is not None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def is_rate_limited(self) -> bool:
|
|
121
|
+
return self.status == 429
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def is_idempotency_key_reused(self) -> bool:
|
|
125
|
+
return self.code == "idempotency_key_reused"
|
|
126
|
+
|
|
127
|
+
def _message(self) -> str:
|
|
128
|
+
if self.problem is None:
|
|
129
|
+
return f"Costfluent API answered {self.status}"
|
|
130
|
+
# The detail, then each field error the detail does not already state. A refused command's
|
|
131
|
+
# detail is only "One or more validation errors occurred", so the fields are what says what
|
|
132
|
+
# to change; a single refusal's detail repeats its one entry, and a missing capability's
|
|
133
|
+
# names it in another case.
|
|
134
|
+
detail = self.detail or ""
|
|
135
|
+
parts = [detail] if detail else []
|
|
136
|
+
for entry in self.field_errors:
|
|
137
|
+
if entry.reason and entry.reason.lower() not in detail.lower():
|
|
138
|
+
named = entry.name and entry.name != GENERAL_ERRORS_FIELD
|
|
139
|
+
parts.append(f"{entry.name}: {entry.reason}" if named else entry.reason)
|
|
140
|
+
text = "; ".join(parts) or self.problem.title
|
|
141
|
+
return f"Costfluent API answered {self.status} {self.problem.code}: {text}"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _is_problem(response: httpx.Response) -> bool:
|
|
145
|
+
return response.headers.get("Content-Type", "").split(";")[0].strip().lower() == PROBLEM_MEDIA_TYPE
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _retryable(request: httpx.Request) -> bool:
|
|
149
|
+
"""Whether a request is safe to resend after a lost response or a 5xx. A POST triggers, so
|
|
150
|
+
resending one the server may have run could run it twice unless it carries an Idempotency-Key,
|
|
151
|
+
and a request with If-Match the server applied would be refused by its own now stale precondition."""
|
|
152
|
+
if "If-Match" in request.headers:
|
|
153
|
+
return False
|
|
154
|
+
if request.method in ("GET", "HEAD", "PUT", "DELETE"):
|
|
155
|
+
return True
|
|
156
|
+
return request.method == "POST" and IDEMPOTENCY_KEY in request.headers
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _should_retry(request: httpx.Request, response: httpx.Response) -> bool:
|
|
160
|
+
"""A 429 is retried for every method: the rate limiter refuses a request before any endpoint runs
|
|
161
|
+
it. A 404 without problem details is the proxy's, not the API's, and is retried as a 5xx is."""
|
|
162
|
+
edge_not_found = response.status_code == 404 and not _is_problem(response)
|
|
163
|
+
return response.status_code == 429 or ((response.status_code >= 500 or edge_not_found) and _retryable(request))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _retry_after(headers: httpx.Headers, now: datetime) -> float | None:
|
|
167
|
+
"""Retry-After in seconds or as an HTTP date, or else the RateLimit-Reset seconds the API sends
|
|
168
|
+
beside it on a 429."""
|
|
169
|
+
value = headers.get("Retry-After") or headers.get("RateLimit-Reset")
|
|
170
|
+
if not value:
|
|
171
|
+
return None
|
|
172
|
+
try:
|
|
173
|
+
return max(float(int(value)), 0.0)
|
|
174
|
+
except ValueError:
|
|
175
|
+
pass
|
|
176
|
+
try:
|
|
177
|
+
at = email.utils.parsedate_to_datetime(value)
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
return None
|
|
180
|
+
if at.tzinfo is None:
|
|
181
|
+
at = at.replace(tzinfo=UTC)
|
|
182
|
+
return max((at - now).total_seconds(), 0.0)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _delay(attempt: int, response: httpx.Response | None) -> float:
|
|
186
|
+
if response is not None and (after := _retry_after(response.headers, _now())) is not None:
|
|
187
|
+
return after
|
|
188
|
+
delay = min(BASE_DELAY * 2**attempt, MAX_DELAY)
|
|
189
|
+
return delay + random.uniform(0, delay / 4)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _RetryTransport(httpx.BaseTransport):
|
|
193
|
+
def __init__(self, inner: httpx.BaseTransport, max_retries: int) -> None:
|
|
194
|
+
self._inner = inner
|
|
195
|
+
self._max_retries = max_retries
|
|
196
|
+
|
|
197
|
+
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
198
|
+
for attempt in itertools.count():
|
|
199
|
+
last = attempt >= self._max_retries
|
|
200
|
+
response: httpx.Response | None
|
|
201
|
+
try:
|
|
202
|
+
response = self._inner.handle_request(request)
|
|
203
|
+
except httpx.TimeoutException:
|
|
204
|
+
# The timeout bounds the call, not each try: three more would quadruple it.
|
|
205
|
+
raise
|
|
206
|
+
except httpx.TransportError:
|
|
207
|
+
if last or not _retryable(request):
|
|
208
|
+
raise
|
|
209
|
+
response = None
|
|
210
|
+
else:
|
|
211
|
+
if last or not _should_retry(request, response):
|
|
212
|
+
return response
|
|
213
|
+
delay = _delay(attempt, response)
|
|
214
|
+
if response is not None:
|
|
215
|
+
if delay > MAX_RETRY_WAIT:
|
|
216
|
+
return response
|
|
217
|
+
response.close()
|
|
218
|
+
_sleep(delay)
|
|
219
|
+
raise AssertionError("unreachable")
|
|
220
|
+
|
|
221
|
+
def close(self) -> None:
|
|
222
|
+
self._inner.close()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class _AsyncRetryTransport(httpx.AsyncBaseTransport):
|
|
226
|
+
def __init__(self, inner: httpx.AsyncBaseTransport, max_retries: int) -> None:
|
|
227
|
+
self._inner = inner
|
|
228
|
+
self._max_retries = max_retries
|
|
229
|
+
|
|
230
|
+
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
231
|
+
for attempt in itertools.count():
|
|
232
|
+
last = attempt >= self._max_retries
|
|
233
|
+
response: httpx.Response | None
|
|
234
|
+
try:
|
|
235
|
+
response = await self._inner.handle_async_request(request)
|
|
236
|
+
except httpx.TimeoutException:
|
|
237
|
+
# The timeout bounds the call, not each try: three more would quadruple it.
|
|
238
|
+
raise
|
|
239
|
+
except httpx.TransportError:
|
|
240
|
+
if last or not _retryable(request):
|
|
241
|
+
raise
|
|
242
|
+
response = None
|
|
243
|
+
else:
|
|
244
|
+
if last or not _should_retry(request, response):
|
|
245
|
+
return response
|
|
246
|
+
delay = _delay(attempt, response)
|
|
247
|
+
if response is not None:
|
|
248
|
+
if delay > MAX_RETRY_WAIT:
|
|
249
|
+
return response
|
|
250
|
+
await response.aclose()
|
|
251
|
+
await _async_sleep(delay)
|
|
252
|
+
raise AssertionError("unreachable")
|
|
253
|
+
|
|
254
|
+
async def aclose(self) -> None:
|
|
255
|
+
await self._inner.aclose()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _add_idempotency_key(request: httpx.Request) -> None:
|
|
259
|
+
"""Every create gets a fresh key unless the caller gave one, so a retry is answered with the
|
|
260
|
+
first response instead of creating twice."""
|
|
261
|
+
if (
|
|
262
|
+
request.method == "POST"
|
|
263
|
+
and IDEMPOTENCY_KEY not in request.headers
|
|
264
|
+
and any(matches(request.url.path) for matches in _CREATES)
|
|
265
|
+
):
|
|
266
|
+
request.headers[IDEMPOTENCY_KEY] = str(uuid.uuid4())
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
270
|
+
if response.status_code >= 400:
|
|
271
|
+
response.read()
|
|
272
|
+
raise CostfluentError.from_response(response)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def _async_add_idempotency_key(request: httpx.Request) -> None:
|
|
276
|
+
_add_idempotency_key(request)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
async def _async_raise_for_status(response: httpx.Response) -> None:
|
|
280
|
+
if response.status_code >= 400:
|
|
281
|
+
await response.aread()
|
|
282
|
+
raise CostfluentError.from_response(response)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def create_client(
|
|
286
|
+
token: str,
|
|
287
|
+
*,
|
|
288
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
289
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
290
|
+
max_retries: int = MAX_RETRIES,
|
|
291
|
+
transport: httpx.BaseTransport | None = None,
|
|
292
|
+
async_transport: httpx.AsyncBaseTransport | None = None,
|
|
293
|
+
) -> AuthenticatedClient:
|
|
294
|
+
"""A client for every generated operation, sync and async, sending ``token`` as a bearer token.
|
|
295
|
+
|
|
296
|
+
Calls retry as described in the README, give each create an Idempotency-Key, and raise
|
|
297
|
+
``CostfluentError`` for any error status. ``transport`` and ``async_transport`` replace httpx's
|
|
298
|
+
own, as ``httpx.MockTransport`` does in a test.
|
|
299
|
+
"""
|
|
300
|
+
headers = {
|
|
301
|
+
"Authorization": f"Bearer {token}",
|
|
302
|
+
"User-Agent": USER_AGENT,
|
|
303
|
+
"Accept": "application/json",
|
|
304
|
+
}
|
|
305
|
+
client = AuthenticatedClient(base_url=base_url, token=token, raise_on_unexpected_status=True)
|
|
306
|
+
client.set_httpx_client(
|
|
307
|
+
httpx.Client(
|
|
308
|
+
base_url=base_url,
|
|
309
|
+
headers=headers,
|
|
310
|
+
timeout=timeout,
|
|
311
|
+
transport=_RetryTransport(transport or httpx.HTTPTransport(), max_retries),
|
|
312
|
+
event_hooks={"request": [_add_idempotency_key], "response": [_raise_for_status]},
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
client.set_async_httpx_client(
|
|
316
|
+
httpx.AsyncClient(
|
|
317
|
+
base_url=base_url,
|
|
318
|
+
headers=headers,
|
|
319
|
+
timeout=timeout,
|
|
320
|
+
transport=_AsyncRetryTransport(async_transport or httpx.AsyncHTTPTransport(), max_retries),
|
|
321
|
+
event_hooks={"request": [_async_add_idempotency_key], "response": [_async_raise_for_status]},
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
return client
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
T = TypeVar("T")
|
|
328
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class _Links(Protocol):
|
|
332
|
+
@property
|
|
333
|
+
def next_(self) -> str | None | Unset: ...
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class _Page(Protocol[T_co]):
|
|
337
|
+
@property
|
|
338
|
+
def links(self) -> _Links: ...
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def data(self) -> Sequence[T_co]: ...
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: ...
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _as_page(result: object) -> _Page[Any]:
|
|
348
|
+
if not (hasattr(result, "links") and hasattr(result, "data")):
|
|
349
|
+
raise TypeError(f"expected a page with links and data, got {type(result).__name__}")
|
|
350
|
+
return result # type: ignore[return-value]
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _next_url(page: _Page[Any], base_url: httpx.URL) -> httpx.URL | None:
|
|
354
|
+
"""The next page on the client's own origin: the token goes only where the client was pointed,
|
|
355
|
+
whatever host the link names."""
|
|
356
|
+
link = page.links.next_
|
|
357
|
+
if not isinstance(link, str) or not link:
|
|
358
|
+
return None
|
|
359
|
+
return base_url.copy_with(raw_path=httpx.URL(link).raw_path)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def paginate(
|
|
363
|
+
operation: Callable[..., _Page[T] | ApiError | None], /, *, client: AuthenticatedClient, **kwargs: Any
|
|
364
|
+
) -> Iterator[T]:
|
|
365
|
+
"""Every item of a list, following ``links.next`` page by page.
|
|
366
|
+
|
|
367
|
+
``operation`` is a generated ``sync`` function, called once with ``client`` and ``kwargs``:
|
|
368
|
+
``paginate(list_workspaces.sync, client=client, limit=100)``.
|
|
369
|
+
"""
|
|
370
|
+
page = _as_page(operation(client=client, **kwargs))
|
|
371
|
+
httpx_client = client.get_httpx_client()
|
|
372
|
+
while True:
|
|
373
|
+
yield from page.data
|
|
374
|
+
url = _next_url(page, httpx_client.base_url)
|
|
375
|
+
if url is None:
|
|
376
|
+
return
|
|
377
|
+
page = type(page).from_dict(httpx_client.get(url).json())
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
async def apaginate(
|
|
381
|
+
operation: Callable[..., Awaitable[_Page[T] | ApiError | None]], /, *, client: AuthenticatedClient, **kwargs: Any
|
|
382
|
+
) -> AsyncIterator[T]:
|
|
383
|
+
"""``paginate`` for a generated ``asyncio`` function."""
|
|
384
|
+
page = _as_page(await operation(client=client, **kwargs))
|
|
385
|
+
httpx_client = client.get_async_httpx_client()
|
|
386
|
+
while True:
|
|
387
|
+
for item in page.data:
|
|
388
|
+
yield item
|
|
389
|
+
url = _next_url(page, httpx_client.base_url)
|
|
390
|
+
if url is None:
|
|
391
|
+
return
|
|
392
|
+
page = type(page).from_dict((await httpx_client.get(url)).json())
|
costfluent/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Contains methods for accessing the API"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Contains endpoint functions for accessing the API"""
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from http import HTTPStatus
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from ... import errors
|
|
7
|
+
from ...client import AuthenticatedClient, Client
|
|
8
|
+
from ...models.allocation_rule_response import AllocationRuleResponse
|
|
9
|
+
from ...models.api_error import ApiError
|
|
10
|
+
from ...models.create_allocation_rule_request import CreateAllocationRuleRequest
|
|
11
|
+
from ...types import UNSET, Response, Unset
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_kwargs(
|
|
15
|
+
*,
|
|
16
|
+
body: CreateAllocationRuleRequest,
|
|
17
|
+
idempotency_key: str | Unset = UNSET,
|
|
18
|
+
) -> dict[str, Any]:
|
|
19
|
+
headers: dict[str, Any] = {}
|
|
20
|
+
if not isinstance(idempotency_key, Unset):
|
|
21
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
22
|
+
|
|
23
|
+
_kwargs: dict[str, Any] = {
|
|
24
|
+
"method": "post",
|
|
25
|
+
"url": "/v1/allocation/rules",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_kwargs["json"] = body.to_dict()
|
|
29
|
+
|
|
30
|
+
headers["Content-Type"] = "application/json"
|
|
31
|
+
|
|
32
|
+
_kwargs["headers"] = headers
|
|
33
|
+
return _kwargs
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _parse_response(
|
|
37
|
+
*, client: AuthenticatedClient | Client, response: httpx.Response
|
|
38
|
+
) -> AllocationRuleResponse | ApiError | None:
|
|
39
|
+
if response.status_code == 201:
|
|
40
|
+
response_201 = AllocationRuleResponse.from_dict(response.json())
|
|
41
|
+
|
|
42
|
+
return response_201
|
|
43
|
+
|
|
44
|
+
if response.status_code == 400:
|
|
45
|
+
response_400 = ApiError.from_dict(response.json())
|
|
46
|
+
|
|
47
|
+
return response_400
|
|
48
|
+
|
|
49
|
+
if response.status_code == 401:
|
|
50
|
+
response_401 = ApiError.from_dict(response.json())
|
|
51
|
+
|
|
52
|
+
return response_401
|
|
53
|
+
|
|
54
|
+
if response.status_code == 403:
|
|
55
|
+
response_403 = ApiError.from_dict(response.json())
|
|
56
|
+
|
|
57
|
+
return response_403
|
|
58
|
+
|
|
59
|
+
if response.status_code == 404:
|
|
60
|
+
response_404 = ApiError.from_dict(response.json())
|
|
61
|
+
|
|
62
|
+
return response_404
|
|
63
|
+
|
|
64
|
+
if response.status_code == 408:
|
|
65
|
+
response_408 = ApiError.from_dict(response.json())
|
|
66
|
+
|
|
67
|
+
return response_408
|
|
68
|
+
|
|
69
|
+
if response.status_code == 409:
|
|
70
|
+
response_409 = ApiError.from_dict(response.json())
|
|
71
|
+
|
|
72
|
+
return response_409
|
|
73
|
+
|
|
74
|
+
if response.status_code == 413:
|
|
75
|
+
response_413 = ApiError.from_dict(response.json())
|
|
76
|
+
|
|
77
|
+
return response_413
|
|
78
|
+
|
|
79
|
+
if response.status_code == 422:
|
|
80
|
+
response_422 = ApiError.from_dict(response.json())
|
|
81
|
+
|
|
82
|
+
return response_422
|
|
83
|
+
|
|
84
|
+
if response.status_code == 429:
|
|
85
|
+
response_429 = ApiError.from_dict(response.json())
|
|
86
|
+
|
|
87
|
+
return response_429
|
|
88
|
+
|
|
89
|
+
if response.status_code == 500:
|
|
90
|
+
response_500 = ApiError.from_dict(response.json())
|
|
91
|
+
|
|
92
|
+
return response_500
|
|
93
|
+
|
|
94
|
+
if response.status_code == 503:
|
|
95
|
+
response_503 = ApiError.from_dict(response.json())
|
|
96
|
+
|
|
97
|
+
return response_503
|
|
98
|
+
|
|
99
|
+
if client.raise_on_unexpected_status:
|
|
100
|
+
raise errors.UnexpectedStatus(response.status_code, response.content)
|
|
101
|
+
else:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _build_response(
|
|
106
|
+
*, client: AuthenticatedClient | Client, response: httpx.Response
|
|
107
|
+
) -> Response[AllocationRuleResponse | ApiError]:
|
|
108
|
+
return Response(
|
|
109
|
+
status_code=HTTPStatus(response.status_code),
|
|
110
|
+
content=response.content,
|
|
111
|
+
headers=response.headers,
|
|
112
|
+
parsed=_parse_response(client=client, response=response),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def sync_detailed(
|
|
117
|
+
*,
|
|
118
|
+
client: AuthenticatedClient,
|
|
119
|
+
body: CreateAllocationRuleRequest,
|
|
120
|
+
idempotency_key: str | Unset = UNSET,
|
|
121
|
+
) -> Response[AllocationRuleResponse | ApiError]:
|
|
122
|
+
"""Create an allocation rule
|
|
123
|
+
|
|
124
|
+
Needs a token with `manageCostAllocation` on the workspace.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
idempotency_key (str | Unset):
|
|
128
|
+
body (CreateAllocationRuleRequest):
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
132
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Response[AllocationRuleResponse | ApiError]
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
kwargs = _get_kwargs(
|
|
139
|
+
body=body,
|
|
140
|
+
idempotency_key=idempotency_key,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
response = client.get_httpx_client().request(
|
|
144
|
+
**kwargs,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return _build_response(client=client, response=response)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def sync(
|
|
151
|
+
*,
|
|
152
|
+
client: AuthenticatedClient,
|
|
153
|
+
body: CreateAllocationRuleRequest,
|
|
154
|
+
idempotency_key: str | Unset = UNSET,
|
|
155
|
+
) -> AllocationRuleResponse | ApiError | None:
|
|
156
|
+
"""Create an allocation rule
|
|
157
|
+
|
|
158
|
+
Needs a token with `manageCostAllocation` on the workspace.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
idempotency_key (str | Unset):
|
|
162
|
+
body (CreateAllocationRuleRequest):
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
166
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
AllocationRuleResponse | ApiError
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
return sync_detailed(
|
|
173
|
+
client=client,
|
|
174
|
+
body=body,
|
|
175
|
+
idempotency_key=idempotency_key,
|
|
176
|
+
).parsed
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def asyncio_detailed(
|
|
180
|
+
*,
|
|
181
|
+
client: AuthenticatedClient,
|
|
182
|
+
body: CreateAllocationRuleRequest,
|
|
183
|
+
idempotency_key: str | Unset = UNSET,
|
|
184
|
+
) -> Response[AllocationRuleResponse | ApiError]:
|
|
185
|
+
"""Create an allocation rule
|
|
186
|
+
|
|
187
|
+
Needs a token with `manageCostAllocation` on the workspace.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
idempotency_key (str | Unset):
|
|
191
|
+
body (CreateAllocationRuleRequest):
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
195
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Response[AllocationRuleResponse | ApiError]
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
kwargs = _get_kwargs(
|
|
202
|
+
body=body,
|
|
203
|
+
idempotency_key=idempotency_key,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
response = await client.get_async_httpx_client().request(**kwargs)
|
|
207
|
+
|
|
208
|
+
return _build_response(client=client, response=response)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def asyncio(
|
|
212
|
+
*,
|
|
213
|
+
client: AuthenticatedClient,
|
|
214
|
+
body: CreateAllocationRuleRequest,
|
|
215
|
+
idempotency_key: str | Unset = UNSET,
|
|
216
|
+
) -> AllocationRuleResponse | ApiError | None:
|
|
217
|
+
"""Create an allocation rule
|
|
218
|
+
|
|
219
|
+
Needs a token with `manageCostAllocation` on the workspace.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
idempotency_key (str | Unset):
|
|
223
|
+
body (CreateAllocationRuleRequest):
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
|
227
|
+
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
AllocationRuleResponse | ApiError
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
await asyncio_detailed(
|
|
235
|
+
client=client,
|
|
236
|
+
body=body,
|
|
237
|
+
idempotency_key=idempotency_key,
|
|
238
|
+
)
|
|
239
|
+
).parsed
|