meteroid 0.27.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.
- meteroid/__init__.py +56 -0
- meteroid/_exceptions.py +93 -0
- meteroid/_version.py +6 -0
- meteroid/api/__init__.py +188 -0
- meteroid/api/_auth.py +231 -0
- meteroid/api/_errors.py +125 -0
- meteroid/api/_pagination.py +258 -0
- meteroid/api/_response.py +132 -0
- meteroid/api/_streaming.py +353 -0
- meteroid/api/add_ons.py +665 -0
- meteroid/api/batch_jobs.py +293 -0
- meteroid/api/checkout_sessions.py +421 -0
- meteroid/api/client.py +967 -0
- meteroid/api/common.py +826 -0
- meteroid/api/connect.py +436 -0
- meteroid/api/coupons.py +631 -0
- meteroid/api/credit_notes.py +450 -0
- meteroid/api/custom_properties.py +495 -0
- meteroid/api/customers.py +1032 -0
- meteroid/api/entitlements.py +254 -0
- meteroid/api/events.py +150 -0
- meteroid/api/features.py +521 -0
- meteroid/api/invoices.py +521 -0
- meteroid/api/metrics.py +554 -0
- meteroid/api/middleware.py +40 -0
- meteroid/api/oauth.py +361 -0
- meteroid/api/oauth_apps.py +393 -0
- meteroid/api/plans.py +1181 -0
- meteroid/api/product_families.py +273 -0
- meteroid/api/products.py +663 -0
- meteroid/api/subscriptions.py +728 -0
- meteroid/api/usage.py +311 -0
- meteroid/errors.py +46 -0
- meteroid/models/__init__.py +1212 -0
- meteroid/models/add_on.py +42 -0
- meteroid/models/add_on_event.py +28 -0
- meteroid/models/add_on_event_data.py +37 -0
- meteroid/models/add_on_id.py +4 -0
- meteroid/models/add_on_list_response.py +20 -0
- meteroid/models/address.py +27 -0
- meteroid/models/all_components_scope.py +11 -0
- meteroid/models/applied_coupon.py +32 -0
- meteroid/models/applied_coupon_detailed.py +20 -0
- meteroid/models/applied_coupon_id.py +4 -0
- meteroid/models/available_parameters.py +24 -0
- meteroid/models/bank_account_id.py +4 -0
- meteroid/models/bank_transfer_payment_method_config.py +17 -0
- meteroid/models/batch_job_chunk_id.py +4 -0
- meteroid/models/batch_job_detail_response.py +51 -0
- meteroid/models/batch_job_failures_response.py +19 -0
- meteroid/models/batch_job_id.py +4 -0
- meteroid/models/batch_job_item_failure_response.py +26 -0
- meteroid/models/batch_job_list_response.py +20 -0
- meteroid/models/batch_job_response.py +39 -0
- meteroid/models/batch_job_status.py +28 -0
- meteroid/models/batch_job_type.py +24 -0
- meteroid/models/billable_metric_id.py +4 -0
- meteroid/models/billing_config.py +17 -0
- meteroid/models/billing_cycle_reset_period.py +11 -0
- meteroid/models/billing_metric_aggregate_enum.py +22 -0
- meteroid/models/billing_period_enum.py +19 -0
- meteroid/models/billing_type.py +15 -0
- meteroid/models/billing_type_enum.py +15 -0
- meteroid/models/boolean_config_value.py +13 -0
- meteroid/models/boolean_effective_entitlement_value.py +13 -0
- meteroid/models/boolean_entitlement_value.py +13 -0
- meteroid/models/boolean_feature_type.py +11 -0
- meteroid/models/boolean_resolved_entitlement_value.py +13 -0
- meteroid/models/calendar_reset_period.py +19 -0
- meteroid/models/calendar_unit.py +18 -0
- meteroid/models/cancel_checkout_session_response.py +17 -0
- meteroid/models/cancel_subscription_request.py +17 -0
- meteroid/models/cancel_subscription_response.py +17 -0
- meteroid/models/capacity_fee.py +24 -0
- meteroid/models/capacity_fee_structure.py +17 -0
- meteroid/models/capacity_plan_fee.py +23 -0
- meteroid/models/capacity_pricing.py +18 -0
- meteroid/models/capacity_threshold.py +18 -0
- meteroid/models/checkout_session.py +59 -0
- meteroid/models/checkout_session_id.py +4 -0
- meteroid/models/checkout_session_status.py +20 -0
- meteroid/models/checkout_type.py +19 -0
- meteroid/models/component_override.py +22 -0
- meteroid/models/component_parameterization.py +20 -0
- meteroid/models/component_parameters.py +21 -0
- meteroid/models/components_scope.py +13 -0
- meteroid/models/config_effective_entitlement_value.py +17 -0
- meteroid/models/config_entitlement_value.py +17 -0
- meteroid/models/config_feature_type.py +21 -0
- meteroid/models/config_resolved_entitlement_value.py +17 -0
- meteroid/models/config_value.py +31 -0
- meteroid/models/config_value_type.py +21 -0
- meteroid/models/connected_account.py +55 -0
- meteroid/models/connected_account_id.py +4 -0
- meteroid/models/connected_accounts_response.py +17 -0
- meteroid/models/connection_status.py +19 -0
- meteroid/models/connection_type.py +15 -0
- meteroid/models/country_code.py +4 -0
- meteroid/models/coupon.py +44 -0
- meteroid/models/coupon_discount.py +20 -0
- meteroid/models/coupon_event.py +28 -0
- meteroid/models/coupon_event_data.py +37 -0
- meteroid/models/coupon_filter.py +17 -0
- meteroid/models/coupon_id.py +4 -0
- meteroid/models/coupon_line_item.py +17 -0
- meteroid/models/coupon_list_response.py +20 -0
- meteroid/models/create_add_on_request.py +28 -0
- meteroid/models/create_checkout_session_request.py +71 -0
- meteroid/models/create_checkout_session_response.py +17 -0
- meteroid/models/create_connected_account_request.py +25 -0
- meteroid/models/create_coupon_request.py +33 -0
- meteroid/models/create_entitlements_request.py +17 -0
- meteroid/models/create_feature_request.py +31 -0
- meteroid/models/create_metric_request.py +43 -0
- meteroid/models/create_o_auth_app_request.py +17 -0
- meteroid/models/create_onboarding_link_request.py +13 -0
- meteroid/models/create_plan_request.py +55 -0
- meteroid/models/create_product_request.py +26 -0
- meteroid/models/create_subscription_add_on.py +22 -0
- meteroid/models/create_subscription_components.py +26 -0
- meteroid/models/credit_note.py +74 -0
- meteroid/models/credit_note_custom_properties_request.py +15 -0
- meteroid/models/credit_note_event.py +28 -0
- meteroid/models/credit_note_event_data.py +62 -0
- meteroid/models/credit_note_id.py +4 -0
- meteroid/models/credit_note_list_response.py +20 -0
- meteroid/models/credit_note_status.py +16 -0
- meteroid/models/credit_type.py +18 -0
- meteroid/models/currency.py +332 -0
- meteroid/models/custom_property_definition.py +40 -0
- meteroid/models/custom_property_definition_create_request.py +36 -0
- meteroid/models/custom_property_definition_id.py +4 -0
- meteroid/models/custom_property_definition_list_response.py +20 -0
- meteroid/models/custom_property_definition_update_request.py +28 -0
- meteroid/models/custom_property_entity_type.py +20 -0
- meteroid/models/custom_property_type.py +34 -0
- meteroid/models/custom_tax_rate.py +17 -0
- meteroid/models/customer.py +72 -0
- meteroid/models/customer_create_request.py +79 -0
- meteroid/models/customer_details.py +31 -0
- meteroid/models/customer_event.py +28 -0
- meteroid/models/customer_event_data.py +32 -0
- meteroid/models/customer_id.py +4 -0
- meteroid/models/customer_list_response.py +20 -0
- meteroid/models/customer_patch_request.py +74 -0
- meteroid/models/customer_payment_method_id.py +4 -0
- meteroid/models/customer_portal_scope.py +15 -0
- meteroid/models/customer_portal_token_request.py +23 -0
- meteroid/models/customer_portal_token_response.py +27 -0
- meteroid/models/customer_type.py +15 -0
- meteroid/models/customer_update_request.py +75 -0
- meteroid/models/decline_kind.py +30 -0
- meteroid/models/double_segmentation_matrix.py +19 -0
- meteroid/models/e_invoicing_finding.py +21 -0
- meteroid/models/e_invoicing_status.py +16 -0
- meteroid/models/effective_entitlement.py +20 -0
- meteroid/models/effective_entitlement_list_response.py +17 -0
- meteroid/models/effective_entitlement_value.py +25 -0
- meteroid/models/entitlement.py +28 -0
- meteroid/models/entitlement_id.py +4 -0
- meteroid/models/entitlement_list_response.py +17 -0
- meteroid/models/entitlement_product_ref.py +19 -0
- meteroid/models/entitlement_spec_request.py +20 -0
- meteroid/models/entitlement_value.py +25 -0
- meteroid/models/error_code.py +30 -0
- meteroid/models/event.py +27 -0
- meteroid/models/event_id.py +4 -0
- meteroid/models/event_type.py +90 -0
- meteroid/models/existing_price_ref.py +17 -0
- meteroid/models/existing_product_ref.py +17 -0
- meteroid/models/external_payment_method_config.py +11 -0
- meteroid/models/extra_component.py +22 -0
- meteroid/models/extra_recurring_billing_type_enum.py +15 -0
- meteroid/models/extra_recurring_fee_structure.py +17 -0
- meteroid/models/extra_recurring_plan_fee.py +25 -0
- meteroid/models/extra_recurring_pricing.py +16 -0
- meteroid/models/feature.py +39 -0
- meteroid/models/feature_id.py +4 -0
- meteroid/models/feature_list_response.py +20 -0
- meteroid/models/feature_ref.py +25 -0
- meteroid/models/feature_status.py +16 -0
- meteroid/models/feature_type.py +22 -0
- meteroid/models/fee.py +34 -0
- meteroid/models/fixed_discount.py +15 -0
- meteroid/models/fixed_window_reset_period.py +19 -0
- meteroid/models/get_checkout_session_response.py +17 -0
- meteroid/models/grouped_usage.py +16 -0
- meteroid/models/ingest_events_request.py +26 -0
- meteroid/models/ingest_events_response.py +18 -0
- meteroid/models/ingest_failure.py +15 -0
- meteroid/models/introspection_request.py +14 -0
- meteroid/models/invoice.py +108 -0
- meteroid/models/invoice_custom_properties_request.py +15 -0
- meteroid/models/invoice_documents_event.py +28 -0
- meteroid/models/invoice_documents_event_data.py +39 -0
- meteroid/models/invoice_event.py +28 -0
- meteroid/models/invoice_event_data.py +42 -0
- meteroid/models/invoice_id.py +4 -0
- meteroid/models/invoice_line_item.py +39 -0
- meteroid/models/invoice_list_response.py +20 -0
- meteroid/models/invoice_payment_status.py +20 -0
- meteroid/models/invoice_status.py +20 -0
- meteroid/models/invoice_type.py +19 -0
- meteroid/models/invoicing_entity_id.py +4 -0
- meteroid/models/json_config_value.py +14 -0
- meteroid/models/linked_segmentation_matrix.py +17 -0
- meteroid/models/list_checkout_sessions_response.py +17 -0
- meteroid/models/matrix_dimension.py +15 -0
- meteroid/models/matrix_plan_pricing.py +17 -0
- meteroid/models/matrix_pricing.py +17 -0
- meteroid/models/matrix_row.py +22 -0
- meteroid/models/metered_effective_entitlement_value.py +20 -0
- meteroid/models/metered_entitlement_spec.py +25 -0
- meteroid/models/metered_entitlement_usage.py +19 -0
- meteroid/models/metered_entitlement_value.py +24 -0
- meteroid/models/metered_feature_type.py +17 -0
- meteroid/models/metered_resolved_entitlement_value.py +25 -0
- meteroid/models/metric.py +50 -0
- meteroid/models/metric_dimension.py +15 -0
- meteroid/models/metric_event.py +28 -0
- meteroid/models/metric_event_data.py +47 -0
- meteroid/models/metric_filter.py +23 -0
- meteroid/models/metric_filter_operator.py +21 -0
- meteroid/models/metric_list_response.py +20 -0
- meteroid/models/metric_segmentation_matrix.py +25 -0
- meteroid/models/metric_summary.py +33 -0
- meteroid/models/metric_usage.py +27 -0
- meteroid/models/minimum_commitment.py +20 -0
- meteroid/models/minimum_commitment_input.py +20 -0
- meteroid/models/minimum_commitment_input_scope.py +20 -0
- meteroid/models/minimum_commitment_scope.py +20 -0
- meteroid/models/never_reset_period.py +11 -0
- meteroid/models/new_product_ref.py +22 -0
- meteroid/models/number_config_value.py +14 -0
- meteroid/models/o_auth_app.py +37 -0
- meteroid/models/o_auth_app_id.py +4 -0
- meteroid/models/o_auth_app_with_secret.py +19 -0
- meteroid/models/o_auth_apps_response.py +17 -0
- meteroid/models/o_auth_error_code.py +34 -0
- meteroid/models/o_auth_error_response.py +21 -0
- meteroid/models/onboarding_link_response.py +16 -0
- meteroid/models/onboarding_mode.py +15 -0
- meteroid/models/one_time_fee.py +16 -0
- meteroid/models/one_time_fee_structure.py +11 -0
- meteroid/models/one_time_plan_fee.py +16 -0
- meteroid/models/one_time_pricing.py +16 -0
- meteroid/models/online_method_config.py +13 -0
- meteroid/models/online_methods_config.py +19 -0
- meteroid/models/online_payment_method_config.py +17 -0
- meteroid/models/organization_id.py +4 -0
- meteroid/models/package_plan_pricing.py +16 -0
- meteroid/models/package_pricing.py +16 -0
- meteroid/models/pagination_response.py +19 -0
- meteroid/models/patch_plan_request.py +17 -0
- meteroid/models/payment_event.py +28 -0
- meteroid/models/payment_method_info.py +23 -0
- meteroid/models/payment_method_type_enum.py +19 -0
- meteroid/models/payment_methods_config.py +27 -0
- meteroid/models/payment_status_enum.py +21 -0
- meteroid/models/payment_transaction_id.py +4 -0
- meteroid/models/payment_type_enum.py +15 -0
- meteroid/models/per_unit_plan_pricing.py +14 -0
- meteroid/models/per_unit_pricing.py +14 -0
- meteroid/models/percentage_discount.py +13 -0
- meteroid/models/plan.py +69 -0
- meteroid/models/plan_add_on_input.py +24 -0
- meteroid/models/plan_event.py +28 -0
- meteroid/models/plan_event_data.py +34 -0
- meteroid/models/plan_id.py +4 -0
- meteroid/models/plan_list_response.py +20 -0
- meteroid/models/plan_status_enum.py +19 -0
- meteroid/models/plan_type_enum.py +16 -0
- meteroid/models/plan_usage_pricing_model.py +31 -0
- meteroid/models/plan_version_id.py +4 -0
- meteroid/models/plan_version_list_response.py +20 -0
- meteroid/models/plan_version_summary.py +26 -0
- meteroid/models/price_component.py +25 -0
- meteroid/models/price_component_id.py +4 -0
- meteroid/models/price_component_input.py +22 -0
- meteroid/models/price_entry.py +20 -0
- meteroid/models/price_id.py +4 -0
- meteroid/models/price_input.py +22 -0
- meteroid/models/pricing.py +34 -0
- meteroid/models/product.py +37 -0
- meteroid/models/product_event.py +28 -0
- meteroid/models/product_event_data.py +30 -0
- meteroid/models/product_family.py +19 -0
- meteroid/models/product_family_create_request.py +13 -0
- meteroid/models/product_family_id.py +4 -0
- meteroid/models/product_family_list_response.py +20 -0
- meteroid/models/product_fee_structure.py +34 -0
- meteroid/models/product_fee_type_enum.py +21 -0
- meteroid/models/product_id.py +4 -0
- meteroid/models/product_list_response.py +20 -0
- meteroid/models/product_ref.py +20 -0
- meteroid/models/products_scope.py +14 -0
- meteroid/models/property_config.py +26 -0
- meteroid/models/quote_event.py +28 -0
- meteroid/models/quote_event_data.py +23 -0
- meteroid/models/quote_id.py +4 -0
- meteroid/models/rate_fee.py +14 -0
- meteroid/models/rate_fee_structure.py +11 -0
- meteroid/models/rate_plan_fee.py +17 -0
- meteroid/models/rate_pricing.py +14 -0
- meteroid/models/recurring_fee.py +22 -0
- meteroid/models/refund_event_data.py +57 -0
- meteroid/models/refund_mode.py +16 -0
- meteroid/models/replace_plan_request.py +50 -0
- meteroid/models/reset_period.py +31 -0
- meteroid/models/resolved_entitlement.py +20 -0
- meteroid/models/resolved_entitlement_list_response.py +17 -0
- meteroid/models/resolved_entitlement_value.py +25 -0
- meteroid/models/rest_error_response.py +19 -0
- meteroid/models/reversal_kind.py +30 -0
- meteroid/models/revocation_request.py +17 -0
- meteroid/models/rotated_secret.py +15 -0
- meteroid/models/select_option.py +15 -0
- meteroid/models/shipping_address.py +19 -0
- meteroid/models/sliding_window_reset_period.py +19 -0
- meteroid/models/slot_downgrade_policy_enum.py +14 -0
- meteroid/models/slot_fee.py +22 -0
- meteroid/models/slot_fee_structure.py +22 -0
- meteroid/models/slot_plan_fee.py +23 -0
- meteroid/models/slot_pricing.py +18 -0
- meteroid/models/slot_upgrade_policy_enum.py +14 -0
- meteroid/models/sub_line_item.py +22 -0
- meteroid/models/subscription.py +99 -0
- meteroid/models/subscription_activation_condition_enum.py +18 -0
- meteroid/models/subscription_add_on.py +30 -0
- meteroid/models/subscription_add_on_customization.py +20 -0
- meteroid/models/subscription_add_on_id.py +4 -0
- meteroid/models/subscription_add_on_parameterization.py +21 -0
- meteroid/models/subscription_add_on_price_override.py +19 -0
- meteroid/models/subscription_component.py +28 -0
- meteroid/models/subscription_coupon.py +36 -0
- meteroid/models/subscription_create_request.py +69 -0
- meteroid/models/subscription_details.py +112 -0
- meteroid/models/subscription_event.py +28 -0
- meteroid/models/subscription_event_data.py +74 -0
- meteroid/models/subscription_fee.py +34 -0
- meteroid/models/subscription_fee_billing_period_enum.py +20 -0
- meteroid/models/subscription_id.py +4 -0
- meteroid/models/subscription_list_response.py +20 -0
- meteroid/models/subscription_status_enum.py +38 -0
- meteroid/models/subscription_update_request.py +36 -0
- meteroid/models/subscription_update_response.py +17 -0
- meteroid/models/subscription_update_type.py +30 -0
- meteroid/models/tax_breakdown_item.py +32 -0
- meteroid/models/tax_exemption_type.py +20 -0
- meteroid/models/tenant_id.py +4 -0
- meteroid/models/term_rate.py +20 -0
- meteroid/models/text_config_value.py +13 -0
- meteroid/models/tier_row.py +20 -0
- meteroid/models/tiered_plan_pricing.py +19 -0
- meteroid/models/tiered_pricing.py +19 -0
- meteroid/models/token_introspection_response.py +25 -0
- meteroid/models/token_request.py +32 -0
- meteroid/models/token_response.py +21 -0
- meteroid/models/transaction.py +62 -0
- meteroid/models/trial_config.py +21 -0
- meteroid/models/unit_conversion.py +19 -0
- meteroid/models/unit_conversion_rounding_enum.py +21 -0
- meteroid/models/update_add_on_request.py +25 -0
- meteroid/models/update_coupon_request.py +22 -0
- meteroid/models/update_entitlement_request.py +17 -0
- meteroid/models/update_feature_request.py +16 -0
- meteroid/models/update_metric_request.py +28 -0
- meteroid/models/update_product_request.py +21 -0
- meteroid/models/usage_fee.py +20 -0
- meteroid/models/usage_fee_structure.py +20 -0
- meteroid/models/usage_model_enum.py +20 -0
- meteroid/models/usage_plan_fee.py +23 -0
- meteroid/models/usage_pricing.py +17 -0
- meteroid/models/usage_pricing_model.py +31 -0
- meteroid/models/usage_response.py +22 -0
- meteroid/models/volume_plan_pricing.py +19 -0
- meteroid/models/volume_pricing.py +19 -0
- meteroid/py.typed +0 -0
- meteroid/serialization.py +907 -0
- meteroid/unions.py +102 -0
- meteroid-0.27.0.dist-info/METADATA +148 -0
- meteroid-0.27.0.dist-info/RECORD +384 -0
- meteroid-0.27.0.dist-info/WHEEL +4 -0
- meteroid-0.27.0.dist-info/licenses/LICENSE +201 -0
meteroid/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ruff: noqa: I001 (the import order depends on the client name)
|
|
2
|
+
from ._version import __version__
|
|
3
|
+
from .api import (
|
|
4
|
+
APIResponse,
|
|
5
|
+
AsyncMeteroid,
|
|
6
|
+
AsyncPage,
|
|
7
|
+
AsyncPaginator,
|
|
8
|
+
AsyncStream,
|
|
9
|
+
Meteroid,
|
|
10
|
+
Stream,
|
|
11
|
+
SyncPage,
|
|
12
|
+
)
|
|
13
|
+
from .errors import (
|
|
14
|
+
MeteroidError,
|
|
15
|
+
APIConnectionError,
|
|
16
|
+
APIError,
|
|
17
|
+
APIResponseValidationError,
|
|
18
|
+
APIStatusError,
|
|
19
|
+
APITimeoutError,
|
|
20
|
+
AuthenticationError,
|
|
21
|
+
BadRequestError,
|
|
22
|
+
ConflictError,
|
|
23
|
+
InternalServerError,
|
|
24
|
+
ModelParseError,
|
|
25
|
+
NotFoundError,
|
|
26
|
+
PermissionDeniedError,
|
|
27
|
+
RateLimitError,
|
|
28
|
+
UnprocessableEntityError,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"Meteroid",
|
|
33
|
+
"MeteroidError",
|
|
34
|
+
"APIConnectionError",
|
|
35
|
+
"APIError",
|
|
36
|
+
"APIResponse",
|
|
37
|
+
"APIResponseValidationError",
|
|
38
|
+
"APIStatusError",
|
|
39
|
+
"APITimeoutError",
|
|
40
|
+
"AsyncMeteroid",
|
|
41
|
+
"AsyncPage",
|
|
42
|
+
"AsyncPaginator",
|
|
43
|
+
"AsyncStream",
|
|
44
|
+
"AuthenticationError",
|
|
45
|
+
"BadRequestError",
|
|
46
|
+
"ConflictError",
|
|
47
|
+
"InternalServerError",
|
|
48
|
+
"ModelParseError",
|
|
49
|
+
"NotFoundError",
|
|
50
|
+
"PermissionDeniedError",
|
|
51
|
+
"RateLimitError",
|
|
52
|
+
"Stream",
|
|
53
|
+
"SyncPage",
|
|
54
|
+
"UnprocessableEntityError",
|
|
55
|
+
"__version__",
|
|
56
|
+
]
|
meteroid/_exceptions.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# this file is @generated by Perseid; edit the shared runtime template.
|
|
2
|
+
"""The errors of a request that got no usable response.
|
|
3
|
+
|
|
4
|
+
Errors carrying an HTTP status are in :mod:`meteroid.api`, as
|
|
5
|
+
:class:`~meteroid.api.APIStatusError` subclasses. Every error derives
|
|
6
|
+
from :class:`MeteroidError`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from .serialization import MeteroidError, ModelParseError
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"APIConnectionError",
|
|
17
|
+
"APIError",
|
|
18
|
+
"APIResponseValidationError",
|
|
19
|
+
"APITimeoutError",
|
|
20
|
+
"MeteroidError",
|
|
21
|
+
"ModelParseError",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class APIError(MeteroidError):
|
|
26
|
+
"""An error of a request to the API."""
|
|
27
|
+
|
|
28
|
+
message: str
|
|
29
|
+
request: httpx.Request | None
|
|
30
|
+
"""The request that failed, when it was built."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, message: str, request: httpx.Request | None) -> None:
|
|
33
|
+
super().__init__(message)
|
|
34
|
+
self.message = message
|
|
35
|
+
self.request = request
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class APIConnectionError(APIError):
|
|
39
|
+
"""The request got no response, after retries: connection failure, reset or timeout."""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self, request: httpx.Request | None, message: str = "Connection error."
|
|
43
|
+
) -> None:
|
|
44
|
+
super().__init__(message, request)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class APITimeoutError(APIConnectionError):
|
|
48
|
+
"""The request timed out, after retries."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, request: httpx.Request | None) -> None:
|
|
51
|
+
super().__init__(request, "Request timed out.")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class APIResponseValidationError(APIError):
|
|
55
|
+
"""A successful response was not valid JSON, or did not match its model."""
|
|
56
|
+
|
|
57
|
+
response: httpx.Response
|
|
58
|
+
status_code: int
|
|
59
|
+
|
|
60
|
+
def __init__(self, response: httpx.Response, reason: str) -> None:
|
|
61
|
+
super().__init__(
|
|
62
|
+
f"Could not decode response ({response.status_code}): {reason}",
|
|
63
|
+
request_of(response),
|
|
64
|
+
)
|
|
65
|
+
self.response = response
|
|
66
|
+
self.status_code = response.status_code
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def raw_body(self) -> bytes:
|
|
70
|
+
"""The response body, empty when it was streamed."""
|
|
71
|
+
try:
|
|
72
|
+
return self.response.content
|
|
73
|
+
except httpx.ResponseNotRead:
|
|
74
|
+
return b""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def request_of(response: httpx.Response) -> httpx.Request | None:
|
|
78
|
+
"""The request of a response, which a middleware may have built without one."""
|
|
79
|
+
try:
|
|
80
|
+
return response.request
|
|
81
|
+
except RuntimeError:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def connection_error(error: httpx.RequestError) -> APIConnectionError:
|
|
86
|
+
"""The SDK error for a transport error of ``httpx``."""
|
|
87
|
+
try:
|
|
88
|
+
request: httpx.Request | None = error.request
|
|
89
|
+
except RuntimeError:
|
|
90
|
+
request = None
|
|
91
|
+
if isinstance(error, httpx.TimeoutException):
|
|
92
|
+
return APITimeoutError(request)
|
|
93
|
+
return APIConnectionError(request, str(error) or "Connection error.")
|
meteroid/_version.py
ADDED
meteroid/api/__init__.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# this file is @generated
|
|
2
|
+
"""Meteroid API resources."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import importlib
|
|
7
|
+
import typing as t
|
|
8
|
+
|
|
9
|
+
from .._exceptions import (
|
|
10
|
+
APIConnectionError,
|
|
11
|
+
APIError,
|
|
12
|
+
APIResponseValidationError,
|
|
13
|
+
APITimeoutError,
|
|
14
|
+
)
|
|
15
|
+
from ..serialization import MeteroidError
|
|
16
|
+
from ._errors import (
|
|
17
|
+
APIStatusError,
|
|
18
|
+
AuthenticationError,
|
|
19
|
+
BadRequestError,
|
|
20
|
+
ConflictError,
|
|
21
|
+
ErrorBody,
|
|
22
|
+
InternalServerError,
|
|
23
|
+
NotFoundError,
|
|
24
|
+
PermissionDeniedError,
|
|
25
|
+
RateLimitError,
|
|
26
|
+
UnprocessableEntityError,
|
|
27
|
+
)
|
|
28
|
+
from ._pagination import AsyncPage, AsyncPaginator, SyncPage
|
|
29
|
+
from ._response import APIResponse
|
|
30
|
+
from ._streaming import (
|
|
31
|
+
AsyncEventStream,
|
|
32
|
+
AsyncStream,
|
|
33
|
+
EventStream,
|
|
34
|
+
SseEvent,
|
|
35
|
+
Stream,
|
|
36
|
+
Upload,
|
|
37
|
+
)
|
|
38
|
+
from .client import AsyncMeteroid, Meteroid
|
|
39
|
+
|
|
40
|
+
if t.TYPE_CHECKING:
|
|
41
|
+
from .add_ons import AddOns, AsyncAddOns
|
|
42
|
+
from .batch_jobs import AsyncBatchJobs, BatchJobs
|
|
43
|
+
from .checkout_sessions import AsyncCheckoutSessions, CheckoutSessions
|
|
44
|
+
from .connect import AsyncConnect, Connect
|
|
45
|
+
from .coupons import AsyncCoupons, Coupons
|
|
46
|
+
from .credit_notes import AsyncCreditNotes, CreditNotes
|
|
47
|
+
from .custom_properties import AsyncCustomProperties, CustomProperties
|
|
48
|
+
from .customers import AsyncCustomers, Customers
|
|
49
|
+
from .entitlements import AsyncEntitlements, Entitlements
|
|
50
|
+
from .events import AsyncEvents, Events
|
|
51
|
+
from .features import AsyncFeatures, Features
|
|
52
|
+
from .invoices import AsyncInvoices, Invoices
|
|
53
|
+
from .metrics import AsyncMetrics, Metrics
|
|
54
|
+
from .oauth import AsyncOauth, Oauth
|
|
55
|
+
from .oauth_apps import AsyncOauthApps, OauthApps
|
|
56
|
+
from .plans import AsyncPlans, Plans
|
|
57
|
+
from .product_families import AsyncProductFamilies, ProductFamilies
|
|
58
|
+
from .products import AsyncProducts, Products
|
|
59
|
+
from .subscriptions import AsyncSubscriptions, Subscriptions
|
|
60
|
+
from .usage import AsyncUsage, Usage
|
|
61
|
+
|
|
62
|
+
# Each resource's module is imported the first time it is used.
|
|
63
|
+
_MODULES: dict[str, str] = {
|
|
64
|
+
"AsyncAddOns": "add_ons",
|
|
65
|
+
"AddOns": "add_ons",
|
|
66
|
+
"AsyncBatchJobs": "batch_jobs",
|
|
67
|
+
"BatchJobs": "batch_jobs",
|
|
68
|
+
"AsyncCheckoutSessions": "checkout_sessions",
|
|
69
|
+
"CheckoutSessions": "checkout_sessions",
|
|
70
|
+
"AsyncConnect": "connect",
|
|
71
|
+
"Connect": "connect",
|
|
72
|
+
"AsyncCoupons": "coupons",
|
|
73
|
+
"Coupons": "coupons",
|
|
74
|
+
"AsyncCreditNotes": "credit_notes",
|
|
75
|
+
"CreditNotes": "credit_notes",
|
|
76
|
+
"AsyncCustomProperties": "custom_properties",
|
|
77
|
+
"CustomProperties": "custom_properties",
|
|
78
|
+
"AsyncCustomers": "customers",
|
|
79
|
+
"Customers": "customers",
|
|
80
|
+
"AsyncEntitlements": "entitlements",
|
|
81
|
+
"Entitlements": "entitlements",
|
|
82
|
+
"AsyncEvents": "events",
|
|
83
|
+
"Events": "events",
|
|
84
|
+
"AsyncFeatures": "features",
|
|
85
|
+
"Features": "features",
|
|
86
|
+
"AsyncInvoices": "invoices",
|
|
87
|
+
"Invoices": "invoices",
|
|
88
|
+
"AsyncMetrics": "metrics",
|
|
89
|
+
"Metrics": "metrics",
|
|
90
|
+
"AsyncOauth": "oauth",
|
|
91
|
+
"Oauth": "oauth",
|
|
92
|
+
"AsyncOauthApps": "oauth_apps",
|
|
93
|
+
"OauthApps": "oauth_apps",
|
|
94
|
+
"AsyncPlans": "plans",
|
|
95
|
+
"Plans": "plans",
|
|
96
|
+
"AsyncProductFamilies": "product_families",
|
|
97
|
+
"ProductFamilies": "product_families",
|
|
98
|
+
"AsyncProducts": "products",
|
|
99
|
+
"Products": "products",
|
|
100
|
+
"AsyncSubscriptions": "subscriptions",
|
|
101
|
+
"Subscriptions": "subscriptions",
|
|
102
|
+
"AsyncUsage": "usage",
|
|
103
|
+
"Usage": "usage",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def __getattr__(name: str) -> t.Any:
|
|
108
|
+
module = _MODULES.get(name)
|
|
109
|
+
if module is None:
|
|
110
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
111
|
+
value = getattr(importlib.import_module(f".{module}", __name__), name)
|
|
112
|
+
globals()[name] = value
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def __dir__() -> list[str]:
|
|
117
|
+
return sorted({*globals(), *_MODULES})
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
__all__ = [
|
|
121
|
+
"APIConnectionError",
|
|
122
|
+
"APIError",
|
|
123
|
+
"APIResponse",
|
|
124
|
+
"APIResponseValidationError",
|
|
125
|
+
"APITimeoutError",
|
|
126
|
+
"AsyncMeteroid",
|
|
127
|
+
"AsyncEventStream",
|
|
128
|
+
"AsyncPage",
|
|
129
|
+
"AsyncPaginator",
|
|
130
|
+
"AsyncStream",
|
|
131
|
+
"EventStream",
|
|
132
|
+
"Meteroid",
|
|
133
|
+
"MeteroidError",
|
|
134
|
+
"SseEvent",
|
|
135
|
+
"Stream",
|
|
136
|
+
"SyncPage",
|
|
137
|
+
"Upload",
|
|
138
|
+
"APIStatusError",
|
|
139
|
+
"AuthenticationError",
|
|
140
|
+
"BadRequestError",
|
|
141
|
+
"ConflictError",
|
|
142
|
+
"ErrorBody",
|
|
143
|
+
"InternalServerError",
|
|
144
|
+
"NotFoundError",
|
|
145
|
+
"PermissionDeniedError",
|
|
146
|
+
"RateLimitError",
|
|
147
|
+
"UnprocessableEntityError",
|
|
148
|
+
"AsyncAddOns",
|
|
149
|
+
"AddOns",
|
|
150
|
+
"AsyncBatchJobs",
|
|
151
|
+
"BatchJobs",
|
|
152
|
+
"AsyncCheckoutSessions",
|
|
153
|
+
"CheckoutSessions",
|
|
154
|
+
"AsyncConnect",
|
|
155
|
+
"Connect",
|
|
156
|
+
"AsyncCoupons",
|
|
157
|
+
"Coupons",
|
|
158
|
+
"AsyncCreditNotes",
|
|
159
|
+
"CreditNotes",
|
|
160
|
+
"AsyncCustomProperties",
|
|
161
|
+
"CustomProperties",
|
|
162
|
+
"AsyncCustomers",
|
|
163
|
+
"Customers",
|
|
164
|
+
"AsyncEntitlements",
|
|
165
|
+
"Entitlements",
|
|
166
|
+
"AsyncEvents",
|
|
167
|
+
"Events",
|
|
168
|
+
"AsyncFeatures",
|
|
169
|
+
"Features",
|
|
170
|
+
"AsyncInvoices",
|
|
171
|
+
"Invoices",
|
|
172
|
+
"AsyncMetrics",
|
|
173
|
+
"Metrics",
|
|
174
|
+
"AsyncOauth",
|
|
175
|
+
"Oauth",
|
|
176
|
+
"AsyncOauthApps",
|
|
177
|
+
"OauthApps",
|
|
178
|
+
"AsyncPlans",
|
|
179
|
+
"Plans",
|
|
180
|
+
"AsyncProductFamilies",
|
|
181
|
+
"ProductFamilies",
|
|
182
|
+
"AsyncProducts",
|
|
183
|
+
"Products",
|
|
184
|
+
"AsyncSubscriptions",
|
|
185
|
+
"Subscriptions",
|
|
186
|
+
"AsyncUsage",
|
|
187
|
+
"Usage",
|
|
188
|
+
]
|
meteroid/api/_auth.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# this file is @generated by Perseid; edit the shared runtime template.
|
|
2
|
+
"""Security schemes: bearer tokens, HTTP basic credentials and API keys."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import base64
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
import typing as t
|
|
11
|
+
import urllib.parse
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"OAuthTokens",
|
|
17
|
+
"Security",
|
|
18
|
+
"SecurityScheme",
|
|
19
|
+
"TokenProvider",
|
|
20
|
+
"apply_auth",
|
|
21
|
+
"async_token",
|
|
22
|
+
"chosen_schemes",
|
|
23
|
+
"needs_token_provider",
|
|
24
|
+
"oauth_scheme",
|
|
25
|
+
"sync_token",
|
|
26
|
+
"token_url",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
TokenProvider = t.Callable[[], str | t.Awaitable[str]]
|
|
30
|
+
"""Returns a fresh bearer token; the async client also accepts coroutine functions."""
|
|
31
|
+
|
|
32
|
+
Security = t.Sequence[t.Sequence[str]]
|
|
33
|
+
"""Alternatives of scheme names: the first one whose credentials are all configured is sent."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SecurityScheme(t.NamedTuple):
|
|
37
|
+
"""How the API reads one kind of credentials."""
|
|
38
|
+
|
|
39
|
+
kind: str
|
|
40
|
+
"""``bearer``, ``basic`` or ``api_key``."""
|
|
41
|
+
location: str = ""
|
|
42
|
+
param: str = ""
|
|
43
|
+
token_url: str = ""
|
|
44
|
+
"""OAuth2 token endpoint of the client credentials flow, absolute or relative to the base URL."""
|
|
45
|
+
scope: str = ""
|
|
46
|
+
"""Space-separated scopes asked for with the access token."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
_EXPIRY_MARGIN = 60.0
|
|
50
|
+
"""Seconds before its expiry that an access token is renewed, or half its life when shorter."""
|
|
51
|
+
|
|
52
|
+
_COOKIE_OCTETS = "!#$%&'()*+-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
|
53
|
+
"""Every RFC 6265 cookie-octet, so that an opaque API key is sent as is."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class OAuthTokens:
|
|
57
|
+
"""OAuth2 client credentials: an access token per token URL and scope, kept until it is about to expire.
|
|
58
|
+
|
|
59
|
+
The client fetches a token on first use with :attr:`client_id` and :attr:`client_secret`,
|
|
60
|
+
sending them in an HTTP basic header (``client_auth="basic"``) or as form fields (``"body"``).
|
|
61
|
+
The lock makes concurrent callers share one token request.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self, client_id: str, client_secret: str, client_auth: str = "basic"
|
|
66
|
+
) -> None:
|
|
67
|
+
if client_auth not in ("basic", "body"):
|
|
68
|
+
raise ValueError("oauth_client_auth must be 'basic' or 'body'")
|
|
69
|
+
self.client_id = client_id
|
|
70
|
+
self.client_secret = client_secret
|
|
71
|
+
self.client_auth = client_auth
|
|
72
|
+
self.lock = threading.Lock()
|
|
73
|
+
self._async_lock: asyncio.Lock | None = None
|
|
74
|
+
self._tokens: dict[str, tuple[str, float]] = {}
|
|
75
|
+
|
|
76
|
+
def async_lock(self) -> asyncio.Lock:
|
|
77
|
+
if self._async_lock is None:
|
|
78
|
+
self._async_lock = asyncio.Lock()
|
|
79
|
+
return self._async_lock
|
|
80
|
+
|
|
81
|
+
def cached(self, url: str) -> str | None:
|
|
82
|
+
"""The access token kept under ``url``, unless it is about to expire."""
|
|
83
|
+
entry = self._tokens.get(url)
|
|
84
|
+
return entry[0] if entry is not None and entry[1] > time.monotonic() else None
|
|
85
|
+
|
|
86
|
+
def store(self, url: str, token: str, expires_in: float | None) -> str:
|
|
87
|
+
"""Keeps ``token``, which lives ``expires_in`` seconds, forever when ``None``."""
|
|
88
|
+
life = float("inf") if expires_in is None else max(expires_in, 0.0)
|
|
89
|
+
self._tokens[url] = (
|
|
90
|
+
token,
|
|
91
|
+
time.monotonic() + life - min(_EXPIRY_MARGIN, life / 2),
|
|
92
|
+
)
|
|
93
|
+
return token
|
|
94
|
+
|
|
95
|
+
def invalidate(self, url: str, token: str) -> None:
|
|
96
|
+
"""Forgets ``token`` after the API rejected it, unless a newer one replaced it."""
|
|
97
|
+
entry = self._tokens.get(url)
|
|
98
|
+
if entry is not None and entry[0] == token:
|
|
99
|
+
del self._tokens[url]
|
|
100
|
+
|
|
101
|
+
def request_parts(self, scope: str) -> tuple[list[tuple[str, str]], dict[str, str]]:
|
|
102
|
+
"""The form fields and headers of a token request."""
|
|
103
|
+
form = [("grant_type", "client_credentials")]
|
|
104
|
+
if scope:
|
|
105
|
+
form.append(("scope", scope))
|
|
106
|
+
if self.client_auth == "body":
|
|
107
|
+
form += [
|
|
108
|
+
("client_id", self.client_id),
|
|
109
|
+
("client_secret", self.client_secret),
|
|
110
|
+
]
|
|
111
|
+
return form, {}
|
|
112
|
+
# RFC 6749 2.3.1: both are form-encoded before the base64.
|
|
113
|
+
raw = f"{urllib.parse.quote_plus(self.client_id)}:{urllib.parse.quote_plus(self.client_secret)}"
|
|
114
|
+
return form, {
|
|
115
|
+
"authorization": f"Basic {base64.b64encode(raw.encode()).decode()}"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def token_url(base_path: str, url: str) -> str:
|
|
120
|
+
"""``url`` as an absolute URL: an absolute-path reference is resolved against the origin of
|
|
121
|
+
the base URL, any other relative one is joined under it."""
|
|
122
|
+
if "://" in url:
|
|
123
|
+
return url
|
|
124
|
+
if url.startswith("/"):
|
|
125
|
+
parts = urllib.parse.urlsplit(base_path)
|
|
126
|
+
return f"{parts.scheme}://{parts.netloc}{url}"
|
|
127
|
+
return f"{base_path}/{url}"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class _Credentials(t.Protocol):
|
|
131
|
+
bearer_access_token: str | None
|
|
132
|
+
token_provider: TokenProvider | None
|
|
133
|
+
basic_auth: tuple[str, str] | None
|
|
134
|
+
api_keys: t.Mapping[str, str]
|
|
135
|
+
security_schemes: t.Mapping[str, SecurityScheme]
|
|
136
|
+
oauth: OAuthTokens | None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _configured(cfg: _Credentials, name: str) -> bool:
|
|
140
|
+
scheme = cfg.security_schemes.get(name)
|
|
141
|
+
if scheme is None:
|
|
142
|
+
return False
|
|
143
|
+
if scheme.kind == "bearer":
|
|
144
|
+
return (
|
|
145
|
+
cfg.token_provider is not None
|
|
146
|
+
or bool(cfg.bearer_access_token)
|
|
147
|
+
or (bool(scheme.token_url) and cfg.oauth is not None)
|
|
148
|
+
)
|
|
149
|
+
if scheme.kind == "basic":
|
|
150
|
+
return cfg.basic_auth is not None
|
|
151
|
+
return bool(cfg.api_keys.get(name) or cfg.bearer_access_token)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def chosen_schemes(cfg: _Credentials, security: Security) -> list[str]:
|
|
155
|
+
"""The schemes of the first alternative of ``security`` whose credentials are all set."""
|
|
156
|
+
for names in security:
|
|
157
|
+
if names and all(_configured(cfg, name) for name in names):
|
|
158
|
+
return list(names)
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def needs_token_provider(cfg: _Credentials, names: t.Sequence[str]) -> bool:
|
|
163
|
+
"""Whether the token provider must be called for the schemes ``names``."""
|
|
164
|
+
return cfg.token_provider is not None and any(
|
|
165
|
+
cfg.security_schemes[name].kind == "bearer" for name in names
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def oauth_scheme(cfg: _Credentials, names: t.Sequence[str]) -> SecurityScheme | None:
|
|
170
|
+
"""The OAuth2 scheme among ``names`` whose token the client fetches, when neither a token
|
|
171
|
+
provider nor a token is set."""
|
|
172
|
+
if cfg.oauth is None or cfg.token_provider is not None or cfg.bearer_access_token:
|
|
173
|
+
return None
|
|
174
|
+
for name in names:
|
|
175
|
+
scheme = cfg.security_schemes[name]
|
|
176
|
+
if scheme.kind == "bearer" and scheme.token_url:
|
|
177
|
+
return scheme
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def sync_token(cfg: _Credentials) -> str:
|
|
182
|
+
"""A token from the provider, which must not be a coroutine function."""
|
|
183
|
+
assert cfg.token_provider is not None
|
|
184
|
+
token = cfg.token_provider()
|
|
185
|
+
if not isinstance(token, str):
|
|
186
|
+
raise TypeError("an async token_provider needs the async client")
|
|
187
|
+
return token
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def async_token(cfg: _Credentials) -> str:
|
|
191
|
+
"""A token from the provider, awaited when it returns an awaitable."""
|
|
192
|
+
assert cfg.token_provider is not None
|
|
193
|
+
token = cfg.token_provider()
|
|
194
|
+
return token if isinstance(token, str) else await token
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def apply_auth(
|
|
198
|
+
cfg: _Credentials,
|
|
199
|
+
names: t.Sequence[str],
|
|
200
|
+
provided_token: str | None,
|
|
201
|
+
headers: httpx.Headers,
|
|
202
|
+
params: list[tuple[str, str]],
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Sets the credentials of the schemes ``names`` on a request's headers and query."""
|
|
205
|
+
for name in names:
|
|
206
|
+
scheme = cfg.security_schemes[name]
|
|
207
|
+
if scheme.kind == "bearer":
|
|
208
|
+
token = (
|
|
209
|
+
provided_token
|
|
210
|
+
if provided_token is not None
|
|
211
|
+
else cfg.bearer_access_token
|
|
212
|
+
)
|
|
213
|
+
headers["authorization"] = f"Bearer {token}"
|
|
214
|
+
elif scheme.kind == "basic":
|
|
215
|
+
assert cfg.basic_auth is not None
|
|
216
|
+
raw = ":".join(cfg.basic_auth).encode()
|
|
217
|
+
headers["authorization"] = f"Basic {base64.b64encode(raw).decode()}"
|
|
218
|
+
else:
|
|
219
|
+
key = cfg.api_keys.get(name) or cfg.bearer_access_token or ""
|
|
220
|
+
if scheme.location == "query":
|
|
221
|
+
params.append((scheme.param, key))
|
|
222
|
+
elif scheme.location == "cookie":
|
|
223
|
+
cookie = (
|
|
224
|
+
f"{scheme.param}={urllib.parse.quote(key, safe=_COOKIE_OCTETS)}"
|
|
225
|
+
)
|
|
226
|
+
previous = headers.get("cookie")
|
|
227
|
+
headers["cookie"] = (
|
|
228
|
+
cookie if previous is None else f"{previous}; {cookie}"
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
headers[scheme.param.lower()] = key
|
meteroid/api/_errors.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# this file is @generated
|
|
2
|
+
"""API errors by status, carrying the decoded error body."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import typing as t
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .. import models as _models
|
|
11
|
+
from .._exceptions import APIError, request_of
|
|
12
|
+
from ..serialization import JSONValue
|
|
13
|
+
from ._response import request_id
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"APIStatusError",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"BadRequestError",
|
|
19
|
+
"ConflictError",
|
|
20
|
+
"ErrorBody",
|
|
21
|
+
"InternalServerError",
|
|
22
|
+
"NotFoundError",
|
|
23
|
+
"PermissionDeniedError",
|
|
24
|
+
"RateLimitError",
|
|
25
|
+
"UnprocessableEntityError",
|
|
26
|
+
"error_class",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
_MAX_DETAIL = 500
|
|
30
|
+
|
|
31
|
+
ErrorBody: t.TypeAlias = _models.OAuthErrorResponse | _models.RestErrorResponse
|
|
32
|
+
"""The error responses the API declares."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class APIStatusError(APIError):
|
|
36
|
+
"""The API answered with a non-2xx status. Subclasses tell the common ones apart."""
|
|
37
|
+
|
|
38
|
+
response: httpx.Response
|
|
39
|
+
status_code: int
|
|
40
|
+
body: ErrorBody | JSONValue
|
|
41
|
+
"""The error response decoded into the schema the operation declares for its
|
|
42
|
+
status; its JSON when it declares none or it does not match; else `None`."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, response: httpx.Response, body: object) -> None:
|
|
45
|
+
detail = _body_text(response) or response.reason_phrase or "error"
|
|
46
|
+
super().__init__(
|
|
47
|
+
f"Error code: {response.status_code} - {detail}", request_of(response)
|
|
48
|
+
)
|
|
49
|
+
self.response = response
|
|
50
|
+
self.status_code = response.status_code
|
|
51
|
+
self.body = t.cast("ErrorBody | JSONValue", body)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def headers(self) -> httpx.Headers:
|
|
55
|
+
"""The response headers."""
|
|
56
|
+
return self.response.headers
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def request_id(self) -> str | None:
|
|
60
|
+
"""The request id to quote to support, when the API sends one."""
|
|
61
|
+
return request_id(self.response.headers)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def raw_body(self) -> bytes:
|
|
65
|
+
"""The response body."""
|
|
66
|
+
return self.response.content
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _body_text(response: httpx.Response) -> str:
|
|
70
|
+
"""The start of the response body, for the error message."""
|
|
71
|
+
try:
|
|
72
|
+
text = response.text.strip()
|
|
73
|
+
except httpx.ResponseNotRead:
|
|
74
|
+
return ""
|
|
75
|
+
return text if len(text) <= _MAX_DETAIL else f"{text[:_MAX_DETAIL]}..."
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class BadRequestError(APIStatusError):
|
|
79
|
+
"""400 Bad Request."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AuthenticationError(APIStatusError):
|
|
83
|
+
"""401 Unauthorized."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PermissionDeniedError(APIStatusError):
|
|
87
|
+
"""403 Forbidden."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class NotFoundError(APIStatusError):
|
|
91
|
+
"""404 Not Found."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ConflictError(APIStatusError):
|
|
95
|
+
"""409 Conflict."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class UnprocessableEntityError(APIStatusError):
|
|
99
|
+
"""422 Unprocessable Entity."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RateLimitError(APIStatusError):
|
|
103
|
+
"""429 Too Many Requests, once retries are exhausted."""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class InternalServerError(APIStatusError):
|
|
107
|
+
"""Any 5xx status, once retries are exhausted."""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
_BY_STATUS: dict[int, type[APIStatusError]] = {
|
|
111
|
+
400: BadRequestError,
|
|
112
|
+
401: AuthenticationError,
|
|
113
|
+
403: PermissionDeniedError,
|
|
114
|
+
404: NotFoundError,
|
|
115
|
+
409: ConflictError,
|
|
116
|
+
422: UnprocessableEntityError,
|
|
117
|
+
429: RateLimitError,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def error_class(status_code: int) -> type[APIStatusError]:
|
|
122
|
+
"""The error raised for a response with this status."""
|
|
123
|
+
if status_code >= 500:
|
|
124
|
+
return InternalServerError
|
|
125
|
+
return _BY_STATUS.get(status_code, APIStatusError)
|