sendandretain 0.2.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.
- sendandretain/__init__.py +67 -0
- sendandretain/_base.py +93 -0
- sendandretain/_client.py +112 -0
- sendandretain/_convenience.py +76 -0
- sendandretain/_exceptions.py +106 -0
- sendandretain/_resources.py +984 -0
- sendandretain/_runtime.py +295 -0
- sendandretain/_version.py +3 -0
- sendandretain/_webhooks.py +78 -0
- sendandretain/api/__init__.py +1 -0
- sendandretain/api/audience/__init__.py +1 -0
- sendandretain/api/audience/create_segment.py +213 -0
- sendandretain/api/audience/delete_segment.py +176 -0
- sendandretain/api/audience/list_segments.py +149 -0
- sendandretain/api/audience/refresh_segment_count.py +188 -0
- sendandretain/api/audience/update_segment.py +202 -0
- sendandretain/api/automations/__init__.py +1 -0
- sendandretain/api/automations/add_automation_steps.py +202 -0
- sendandretain/api/automations/archive_automation.py +184 -0
- sendandretain/api/automations/backfill_missed_enrollments.py +243 -0
- sendandretain/api/automations/create_automation.py +221 -0
- sendandretain/api/automations/duplicate_automation.py +211 -0
- sendandretain/api/automations/get_automation.py +189 -0
- sendandretain/api/automations/get_automation_metrics.py +222 -0
- sendandretain/api/automations/list_automation_runs.py +240 -0
- sendandretain/api/automations/list_automations.py +157 -0
- sendandretain/api/automations/move_automation_step.py +198 -0
- sendandretain/api/automations/preflight_automation.py +197 -0
- sendandretain/api/automations/promote_ab_winner.py +218 -0
- sendandretain/api/automations/remove_automation_step.py +198 -0
- sendandretain/api/automations/set_ab_test.py +214 -0
- sendandretain/api/automations/set_automation_status.py +227 -0
- sendandretain/api/automations/sync_automation_step_props.py +202 -0
- sendandretain/api/automations/update_automation.py +218 -0
- sendandretain/api/automations/update_automation_step.py +202 -0
- sendandretain/api/contacts/__init__.py +1 -0
- sendandretain/api/contacts/delete_contact.py +188 -0
- sendandretain/api/contacts/get_contact.py +184 -0
- sendandretain/api/contacts/import_contacts.py +221 -0
- sendandretain/api/contacts/list_contact_events.py +244 -0
- sendandretain/api/contacts/list_contacts.py +233 -0
- sendandretain/api/contacts/upsert_contact.py +192 -0
- sendandretain/api/emails/__init__.py +1 -0
- sendandretain/api/emails/cancel_email.py +188 -0
- sendandretain/api/emails/get_message.py +182 -0
- sendandretain/api/emails/list_emails.py +253 -0
- sendandretain/api/emails/reschedule_email.py +209 -0
- sendandretain/api/emails/send_email.py +273 -0
- sendandretain/api/emails/send_email_batch.py +263 -0
- sendandretain/api/events/__init__.py +1 -0
- sendandretain/api/events/emit_event.py +224 -0
- sendandretain/api/metrics/__init__.py +1 -0
- sendandretain/api/metrics/get_metrics.py +219 -0
- sendandretain/api/metrics/get_metrics_trends.py +197 -0
- sendandretain/api/metrics/get_queue_health.py +165 -0
- sendandretain/api/settings/__init__.py +1 -0
- sendandretain/api/settings/get_brand.py +165 -0
- sendandretain/api/settings/get_project_settings.py +161 -0
- sendandretain/api/settings/update_brand.py +193 -0
- sendandretain/api/settings/update_project_settings.py +209 -0
- sendandretain/api/setup/__init__.py +1 -0
- sendandretain/api/setup/create_domain.py +225 -0
- sendandretain/api/setup/create_sender.py +213 -0
- sendandretain/api/setup/delete_domain.py +205 -0
- sendandretain/api/setup/delete_sender.py +197 -0
- sendandretain/api/setup/get_connection_status.py +173 -0
- sendandretain/api/setup/get_onboarding_steps.py +181 -0
- sendandretain/api/setup/list_domains.py +149 -0
- sendandretain/api/setup/list_senders.py +149 -0
- sendandretain/api/setup/register_provider_webhook.py +177 -0
- sendandretain/api/setup/register_webhooks.py +169 -0
- sendandretain/api/setup/update_sender.py +218 -0
- sendandretain/api/setup/verify_domain.py +192 -0
- sendandretain/api/suppressions/__init__.py +1 -0
- sendandretain/api/suppressions/add_suppression.py +209 -0
- sendandretain/api/suppressions/import_suppressions.py +217 -0
- sendandretain/api/suppressions/list_suppressions.py +219 -0
- sendandretain/api/suppressions/remove_suppression.py +193 -0
- sendandretain/api/templates/__init__.py +1 -0
- sendandretain/api/templates/add_template_translation.py +214 -0
- sendandretain/api/templates/archive_template.py +193 -0
- sendandretain/api/templates/create_template.py +213 -0
- sendandretain/api/templates/get_template.py +210 -0
- sendandretain/api/templates/list_template_versions.py +189 -0
- sendandretain/api/templates/list_templates.py +157 -0
- sendandretain/api/templates/publish_template_version.py +219 -0
- sendandretain/api/templates/render_template.py +219 -0
- sendandretain/api/templates/send_template_test.py +218 -0
- sendandretain/api/templates/unpublish_template.py +205 -0
- sendandretain/api/templates/update_template.py +214 -0
- sendandretain/api/templates/update_template_meta.py +218 -0
- sendandretain/api/webhooks/__init__.py +1 -0
- sendandretain/api/webhooks/create_webhook_endpoint.py +225 -0
- sendandretain/api/webhooks/delete_webhook_endpoint.py +193 -0
- sendandretain/api/webhooks/get_webhook_endpoint.py +181 -0
- sendandretain/api/webhooks/list_webhook_deliveries.py +267 -0
- sendandretain/api/webhooks/list_webhook_endpoints.py +157 -0
- sendandretain/api/webhooks/replay_webhook_delivery.py +216 -0
- sendandretain/api/webhooks/rotate_webhook_secret.py +219 -0
- sendandretain/api/webhooks/test_webhook_endpoint.py +197 -0
- sendandretain/api/webhooks/update_webhook_endpoint.py +214 -0
- sendandretain/client.py +268 -0
- sendandretain/errors.py +16 -0
- sendandretain/models/__init__.py +819 -0
- sendandretain/models/add_automation_steps_body.py +262 -0
- sendandretain/models/add_automation_steps_response_200.py +70 -0
- sendandretain/models/add_automation_steps_schema_0_variant_0.py +146 -0
- sendandretain/models/add_automation_steps_schema_0_variant_0_props_overrides.py +47 -0
- sendandretain/models/add_automation_steps_schema_0_variant_0_send_window.py +91 -0
- sendandretain/models/add_automation_steps_schema_0_variant_1.py +52 -0
- sendandretain/models/add_automation_steps_schema_0_variant_2.py +114 -0
- sendandretain/models/add_automation_steps_schema_0_variant_2_send_window.py +91 -0
- sendandretain/models/add_automation_steps_schema_0_variant_3.py +140 -0
- sendandretain/models/add_automation_steps_schema_0_variant_3_attributes.py +47 -0
- sendandretain/models/add_automation_steps_schema_0_variant_3_send_window.py +91 -0
- sendandretain/models/add_automation_steps_schema_0_variant_4.py +124 -0
- sendandretain/models/add_automation_steps_schema_0_variant_4_method.py +9 -0
- sendandretain/models/add_automation_steps_schema_0_variant_4_send_window.py +91 -0
- sendandretain/models/add_automation_steps_schema_0_variant_5.py +83 -0
- sendandretain/models/add_automation_steps_schema_0_variant_6.py +71 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7.py +712 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_branches_filter_variant_0_item.py +96 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_branches_filter_variant_0_item_op.py +17 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_branches_item.py +310 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_filter_variant_0_item.py +96 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_filter_variant_0_item_op.py +17 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_if_match.py +9 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_if_no_match.py +9 -0
- sendandretain/models/add_automation_steps_schema_0_variant_7_send_window.py +91 -0
- sendandretain/models/add_automation_steps_schema_1.py +114 -0
- sendandretain/models/add_automation_steps_schema_1_conditions_variant_0.py +92 -0
- sendandretain/models/add_automation_steps_schema_1_conditions_variant_0_op.py +17 -0
- sendandretain/models/add_automation_steps_schema_1_op.py +9 -0
- sendandretain/models/add_suppression_body.py +90 -0
- sendandretain/models/add_suppression_body_reason.py +11 -0
- sendandretain/models/add_suppression_response_201.py +70 -0
- sendandretain/models/add_template_translation_body.py +80 -0
- sendandretain/models/add_template_translation_response_201.py +96 -0
- sendandretain/models/add_template_translation_response_201_status.py +8 -0
- sendandretain/models/archive_automation_response_200.py +70 -0
- sendandretain/models/archive_template_response_200.py +70 -0
- sendandretain/models/automations_id_ab_test_post_request_body_content_application_json_variants_variant_0_item.py +81 -0
- sendandretain/models/automations_id_patch_request_body_content_application_json_trigger_filter_variant_0_variant_0_item.py +96 -0
- sendandretain/models/automations_id_patch_request_body_content_application_json_trigger_filter_variant_0_variant_0_item_op.py +17 -0
- sendandretain/models/automations_post_request_body_content_application_json_trigger_filter_variant_0_item.py +96 -0
- sendandretain/models/automations_post_request_body_content_application_json_trigger_filter_variant_0_item_op.py +17 -0
- sendandretain/models/automations_steps_put_request_body_content_application_json_patch_branches_filter_variant_0_item.py +96 -0
- sendandretain/models/automations_steps_put_request_body_content_application_json_patch_branches_filter_variant_0_item_op.py +17 -0
- sendandretain/models/automations_steps_put_request_body_content_application_json_patch_filter_variant_0_item.py +96 -0
- sendandretain/models/automations_steps_put_request_body_content_application_json_patch_filter_variant_0_item_op.py +17 -0
- sendandretain/models/backfill_missed_enrollments_body.py +79 -0
- sendandretain/models/backfill_missed_enrollments_response_200.py +115 -0
- sendandretain/models/cancel_email_response_200.py +78 -0
- sendandretain/models/cancel_email_response_200_status.py +8 -0
- sendandretain/models/contact_result.py +69 -0
- sendandretain/models/create_automation_body.py +378 -0
- sendandretain/models/create_automation_response_201.py +87 -0
- sendandretain/models/create_automation_response_201_status.py +8 -0
- sendandretain/models/create_automation_schema_0.py +110 -0
- sendandretain/models/create_automation_schema_0_conditions_variant_0.py +92 -0
- sendandretain/models/create_automation_schema_0_conditions_variant_0_op.py +17 -0
- sendandretain/models/create_automation_schema_0_op.py +9 -0
- sendandretain/models/create_automation_schema_1_variant_0.py +144 -0
- sendandretain/models/create_automation_schema_1_variant_0_props_overrides.py +47 -0
- sendandretain/models/create_automation_schema_1_variant_0_send_window.py +91 -0
- sendandretain/models/create_automation_schema_1_variant_1.py +52 -0
- sendandretain/models/create_automation_schema_1_variant_2.py +112 -0
- sendandretain/models/create_automation_schema_1_variant_2_send_window.py +91 -0
- sendandretain/models/create_automation_schema_1_variant_3.py +136 -0
- sendandretain/models/create_automation_schema_1_variant_3_attributes.py +47 -0
- sendandretain/models/create_automation_schema_1_variant_3_send_window.py +91 -0
- sendandretain/models/create_automation_schema_1_variant_4.py +121 -0
- sendandretain/models/create_automation_schema_1_variant_4_method.py +9 -0
- sendandretain/models/create_automation_schema_1_variant_4_send_window.py +91 -0
- sendandretain/models/create_automation_schema_1_variant_5.py +83 -0
- sendandretain/models/create_automation_schema_1_variant_6.py +71 -0
- sendandretain/models/create_automation_schema_1_variant_7.py +703 -0
- sendandretain/models/create_automation_schema_1_variant_7_branches_filter_variant_0_item.py +96 -0
- sendandretain/models/create_automation_schema_1_variant_7_branches_filter_variant_0_item_op.py +17 -0
- sendandretain/models/create_automation_schema_1_variant_7_branches_item.py +313 -0
- sendandretain/models/create_automation_schema_1_variant_7_filter_variant_0_item.py +96 -0
- sendandretain/models/create_automation_schema_1_variant_7_filter_variant_0_item_op.py +17 -0
- sendandretain/models/create_automation_schema_1_variant_7_if_match.py +9 -0
- sendandretain/models/create_automation_schema_1_variant_7_if_no_match.py +9 -0
- sendandretain/models/create_automation_schema_1_variant_7_send_window.py +91 -0
- sendandretain/models/create_domain_body.py +61 -0
- sendandretain/models/create_domain_response_201.py +161 -0
- sendandretain/models/create_domain_response_201_dmarc.py +110 -0
- sendandretain/models/create_domain_response_201_dmarc_recommended_record.py +89 -0
- sendandretain/models/create_domain_response_201_dns_records_item.py +99 -0
- sendandretain/models/create_domain_response_201_tracking_type_0.py +115 -0
- sendandretain/models/create_segment_body.py +105 -0
- sendandretain/models/create_segment_body_filter_item.py +81 -0
- sendandretain/models/create_segment_body_filter_item_op.py +17 -0
- sendandretain/models/create_segment_response_201.py +70 -0
- sendandretain/models/create_sender_body.py +100 -0
- sendandretain/models/create_sender_response_201.py +88 -0
- sendandretain/models/create_template_body.py +136 -0
- sendandretain/models/create_template_body_category.py +9 -0
- sendandretain/models/create_template_body_variables_item.py +107 -0
- sendandretain/models/create_template_body_variables_item_type.py +12 -0
- sendandretain/models/create_template_response_201.py +96 -0
- sendandretain/models/create_template_response_201_status.py +8 -0
- sendandretain/models/create_webhook_endpoint_body.py +93 -0
- sendandretain/models/create_webhook_endpoint_response_201.py +303 -0
- sendandretain/models/delete_contact_response_200.py +70 -0
- sendandretain/models/delete_domain_response_200.py +91 -0
- sendandretain/models/delete_segment_response_200.py +70 -0
- sendandretain/models/delete_sender_response_200.py +70 -0
- sendandretain/models/delete_webhook_endpoint_response_200.py +61 -0
- sendandretain/models/duplicate_automation_response_201.py +87 -0
- sendandretain/models/duplicate_automation_response_201_status.py +8 -0
- sendandretain/models/emit_event_body.py +151 -0
- sendandretain/models/emit_event_body_contact_attributes.py +47 -0
- sendandretain/models/emit_event_body_properties.py +47 -0
- sendandretain/models/emit_event_response_202.py +71 -0
- sendandretain/models/emit_event_response_202_status.py +8 -0
- sendandretain/models/error.py +68 -0
- sendandretain/models/error_error.py +143 -0
- sendandretain/models/error_error_code.py +24 -0
- sendandretain/models/error_error_key_scope.py +10 -0
- sendandretain/models/error_error_required_grant.py +8 -0
- sendandretain/models/error_error_required_scope.py +10 -0
- sendandretain/models/event_result.py +121 -0
- sendandretain/models/event_result_skipped_item.py +81 -0
- sendandretain/models/event_result_skipped_item_reason.py +13 -0
- sendandretain/models/get_automation_metrics_response_200.py +152 -0
- sendandretain/models/get_automation_metrics_response_200_ab_tests_item.py +132 -0
- sendandretain/models/get_automation_metrics_response_200_ab_tests_item_arms_item.py +88 -0
- sendandretain/models/get_automation_metrics_response_200_by_step.py +47 -0
- sendandretain/models/get_automation_metrics_response_200_by_variant.py +47 -0
- sendandretain/models/get_automation_metrics_response_200_run_counts.py +47 -0
- sendandretain/models/get_automation_response_200.py +215 -0
- sendandretain/models/get_automation_response_200_steps_item.py +203 -0
- sendandretain/models/get_automation_response_200_steps_item_config_type_0.py +47 -0
- sendandretain/models/get_brand_response_200.py +103 -0
- sendandretain/models/get_brand_response_200_brand.py +47 -0
- sendandretain/models/get_connection_status_response_200.py +188 -0
- sendandretain/models/get_connection_status_response_200_domains_item.py +47 -0
- sendandretain/models/get_connection_status_response_200_provider_type_0.py +99 -0
- sendandretain/models/get_connection_status_response_200_senders_item.py +47 -0
- sendandretain/models/get_contact_response_200.py +47 -0
- sendandretain/models/get_metrics_response_200.py +170 -0
- sendandretain/models/get_metrics_response_200_by_day.py +47 -0
- sendandretain/models/get_metrics_response_200_by_template.py +47 -0
- sendandretain/models/get_metrics_response_200_period.py +85 -0
- sendandretain/models/get_metrics_response_200_source.py +9 -0
- sendandretain/models/get_metrics_response_200_totals.py +47 -0
- sendandretain/models/get_metrics_trends_response_200.py +106 -0
- sendandretain/models/get_metrics_trends_response_200_series_item.py +135 -0
- sendandretain/models/get_metrics_trends_response_200_totals.py +47 -0
- sendandretain/models/get_onboarding_steps_response_200.py +148 -0
- sendandretain/models/get_onboarding_steps_response_200_steps_item.py +47 -0
- sendandretain/models/get_project_settings_response_200.py +146 -0
- sendandretain/models/get_project_settings_response_200_utm_type_0.py +47 -0
- sendandretain/models/get_queue_health_response_200.py +99 -0
- sendandretain/models/get_template_response_200.py +189 -0
- sendandretain/models/get_template_response_200_variables_item.py +47 -0
- sendandretain/models/get_webhook_endpoint_response_200.py +294 -0
- sendandretain/models/import_contacts_body.py +76 -0
- sendandretain/models/import_contacts_body_contacts_item.py +116 -0
- sendandretain/models/import_contacts_body_contacts_item_attributes.py +47 -0
- sendandretain/models/import_contacts_response_200.py +81 -0
- sendandretain/models/import_suppressions_body.py +76 -0
- sendandretain/models/import_suppressions_body_entries_item.py +89 -0
- sendandretain/models/import_suppressions_body_entries_item_reason.py +9 -0
- sendandretain/models/import_suppressions_response_200.py +81 -0
- sendandretain/models/list_automation_runs_response_200.py +107 -0
- sendandretain/models/list_automation_runs_response_200_data_item.py +305 -0
- sendandretain/models/list_automation_runs_response_200_data_item_variant_assignments.py +47 -0
- sendandretain/models/list_automation_runs_response_200_object.py +8 -0
- sendandretain/models/list_automations_response_200.py +107 -0
- sendandretain/models/list_automations_response_200_data_item.py +106 -0
- sendandretain/models/list_automations_response_200_object.py +8 -0
- sendandretain/models/list_contact_events_response_200.py +107 -0
- sendandretain/models/list_contact_events_response_200_data_item.py +149 -0
- sendandretain/models/list_contact_events_response_200_data_item_properties.py +47 -0
- sendandretain/models/list_contact_events_response_200_object.py +8 -0
- sendandretain/models/list_contacts_response_200.py +107 -0
- sendandretain/models/list_contacts_response_200_data_item.py +47 -0
- sendandretain/models/list_contacts_response_200_object.py +8 -0
- sendandretain/models/list_domains_response_200.py +107 -0
- sendandretain/models/list_domains_response_200_data_item.py +346 -0
- sendandretain/models/list_domains_response_200_data_item_dns_records_item.py +47 -0
- sendandretain/models/list_domains_response_200_object.py +8 -0
- sendandretain/models/list_emails_response_200.py +107 -0
- sendandretain/models/list_emails_response_200_object.py +8 -0
- sendandretain/models/list_envelope.py +94 -0
- sendandretain/models/list_envelope_object.py +8 -0
- sendandretain/models/list_segments_response_200.py +107 -0
- sendandretain/models/list_segments_response_200_data_item.py +200 -0
- sendandretain/models/list_segments_response_200_data_item_filter_item.py +47 -0
- sendandretain/models/list_segments_response_200_object.py +8 -0
- sendandretain/models/list_senders_response_200.py +107 -0
- sendandretain/models/list_senders_response_200_data_item.py +154 -0
- sendandretain/models/list_senders_response_200_object.py +8 -0
- sendandretain/models/list_suppressions_reason.py +12 -0
- sendandretain/models/list_suppressions_response_200.py +107 -0
- sendandretain/models/list_suppressions_response_200_data_item.py +47 -0
- sendandretain/models/list_suppressions_response_200_object.py +8 -0
- sendandretain/models/list_template_versions_response_200.py +107 -0
- sendandretain/models/list_template_versions_response_200_data_item.py +145 -0
- sendandretain/models/list_template_versions_response_200_object.py +8 -0
- sendandretain/models/list_templates_response_200.py +107 -0
- sendandretain/models/list_templates_response_200_data_item.py +154 -0
- sendandretain/models/list_templates_response_200_object.py +8 -0
- sendandretain/models/list_webhook_deliveries_response_200.py +107 -0
- sendandretain/models/list_webhook_deliveries_response_200_data_item.py +244 -0
- sendandretain/models/list_webhook_deliveries_response_200_object.py +8 -0
- sendandretain/models/list_webhook_endpoints_response_200.py +107 -0
- sendandretain/models/list_webhook_endpoints_response_200_data_item.py +294 -0
- sendandretain/models/list_webhook_endpoints_response_200_object.py +8 -0
- sendandretain/models/message_status.py +350 -0
- sendandretain/models/message_status_events_item.py +87 -0
- sendandretain/models/move_automation_step_body.py +97 -0
- sendandretain/models/move_automation_step_body_direction.py +9 -0
- sendandretain/models/move_automation_step_response_200.py +61 -0
- sendandretain/models/preflight_automation_response_200.py +88 -0
- sendandretain/models/preflight_automation_response_200_checks_item.py +88 -0
- sendandretain/models/promote_ab_winner_body.py +61 -0
- sendandretain/models/promote_ab_winner_response_200.py +70 -0
- sendandretain/models/publish_template_version_body.py +61 -0
- sendandretain/models/publish_template_version_response_200.py +99 -0
- sendandretain/models/refresh_segment_count_response_200.py +70 -0
- sendandretain/models/register_provider_webhook_response_200.py +70 -0
- sendandretain/models/register_webhooks_response_200.py +70 -0
- sendandretain/models/remove_automation_step_body.py +69 -0
- sendandretain/models/remove_automation_step_response_200.py +61 -0
- sendandretain/models/remove_suppression_response_200.py +70 -0
- sendandretain/models/render_template_body.py +84 -0
- sendandretain/models/render_template_body_props.py +47 -0
- sendandretain/models/render_template_response_200.py +120 -0
- sendandretain/models/render_template_response_200_props_used.py +47 -0
- sendandretain/models/replay_webhook_delivery_response_202.py +61 -0
- sendandretain/models/reschedule_email_body.py +61 -0
- sendandretain/models/reschedule_email_response_200.py +87 -0
- sendandretain/models/reschedule_email_response_200_status.py +8 -0
- sendandretain/models/rotate_webhook_secret_body.py +63 -0
- sendandretain/models/rotate_webhook_secret_response_200.py +78 -0
- sendandretain/models/send_email_batch_body.py +76 -0
- sendandretain/models/send_email_batch_body_emails_item.py +282 -0
- sendandretain/models/send_email_batch_body_emails_item_attachments_item.py +80 -0
- sendandretain/models/send_email_batch_body_emails_item_headers.py +47 -0
- sendandretain/models/send_email_batch_body_emails_item_props.py +47 -0
- sendandretain/models/send_email_batch_body_emails_item_tags_item.py +69 -0
- sendandretain/models/send_email_batch_response_202.py +97 -0
- sendandretain/models/send_email_batch_response_202_data_item.py +110 -0
- sendandretain/models/send_email_batch_response_202_data_item_error.py +71 -0
- sendandretain/models/send_email_body.py +279 -0
- sendandretain/models/send_email_body_attachments_item.py +80 -0
- sendandretain/models/send_email_body_headers.py +47 -0
- sendandretain/models/send_email_body_props.py +47 -0
- sendandretain/models/send_email_body_tags_item.py +69 -0
- sendandretain/models/send_result.py +82 -0
- sendandretain/models/send_result_status.py +10 -0
- sendandretain/models/send_template_test_body.py +94 -0
- sendandretain/models/send_template_test_body_props.py +47 -0
- sendandretain/models/send_template_test_response_201.py +79 -0
- sendandretain/models/set_ab_test_body.py +115 -0
- sendandretain/models/set_ab_test_response_200.py +92 -0
- sendandretain/models/set_automation_status_body.py +73 -0
- sendandretain/models/set_automation_status_body_status.py +9 -0
- sendandretain/models/set_automation_status_response_200.py +70 -0
- sendandretain/models/settings_patch_request_body_content_application_json_utm_variant_0.py +79 -0
- sendandretain/models/settings_patch_request_body_content_application_json_utm_variant_0_params.py +47 -0
- sendandretain/models/sync_automation_step_props_body.py +95 -0
- sendandretain/models/sync_automation_step_props_body_updates_item.py +79 -0
- sendandretain/models/sync_automation_step_props_body_updates_item_props_overrides.py +47 -0
- sendandretain/models/sync_automation_step_props_response_200.py +72 -0
- sendandretain/models/test_webhook_endpoint_response_202.py +61 -0
- sendandretain/models/unpublish_template_response_200.py +70 -0
- sendandretain/models/update_automation_body.py +247 -0
- sendandretain/models/update_automation_response_200.py +61 -0
- sendandretain/models/update_automation_schema_0.py +110 -0
- sendandretain/models/update_automation_schema_0_conditions_variant_0.py +92 -0
- sendandretain/models/update_automation_schema_0_conditions_variant_0_op.py +17 -0
- sendandretain/models/update_automation_schema_0_op.py +9 -0
- sendandretain/models/update_automation_step_body.py +83 -0
- sendandretain/models/update_automation_step_body_patch.py +390 -0
- sendandretain/models/update_automation_step_body_patch_attributes.py +47 -0
- sendandretain/models/update_automation_step_body_patch_branches_item.py +134 -0
- sendandretain/models/update_automation_step_body_patch_if_match.py +9 -0
- sendandretain/models/update_automation_step_body_patch_if_no_match.py +9 -0
- sendandretain/models/update_automation_step_body_patch_method.py +9 -0
- sendandretain/models/update_automation_step_body_patch_props_overrides.py +47 -0
- sendandretain/models/update_automation_step_body_patch_send_window.py +91 -0
- sendandretain/models/update_automation_step_response_200.py +61 -0
- sendandretain/models/update_brand_body.py +68 -0
- sendandretain/models/update_brand_body_brand.py +170 -0
- sendandretain/models/update_brand_body_brand_colors.py +97 -0
- sendandretain/models/update_brand_body_brand_fonts.py +70 -0
- sendandretain/models/update_brand_body_brand_logo.py +70 -0
- sendandretain/models/update_brand_body_brand_social_links_item.py +71 -0
- sendandretain/models/update_brand_body_brand_social_links_item_platform.py +15 -0
- sendandretain/models/update_brand_body_brand_voice.py +72 -0
- sendandretain/models/update_brand_response_200.py +83 -0
- sendandretain/models/update_brand_response_200_brand.py +47 -0
- sendandretain/models/update_project_settings_body.py +149 -0
- sendandretain/models/update_project_settings_response_200.py +128 -0
- sendandretain/models/update_project_settings_response_200_utm_type_0.py +47 -0
- sendandretain/models/update_segment_body.py +109 -0
- sendandretain/models/update_segment_body_filter_item.py +81 -0
- sendandretain/models/update_segment_body_filter_item_op.py +17 -0
- sendandretain/models/update_segment_response_200.py +61 -0
- sendandretain/models/update_sender_body.py +90 -0
- sendandretain/models/update_sender_response_200.py +70 -0
- sendandretain/models/update_step_schema_0.py +108 -0
- sendandretain/models/update_step_schema_0_conditions_variant_0.py +91 -0
- sendandretain/models/update_step_schema_0_conditions_variant_0_op.py +17 -0
- sendandretain/models/update_step_schema_0_op.py +9 -0
- sendandretain/models/update_template_body.py +132 -0
- sendandretain/models/update_template_body_category.py +9 -0
- sendandretain/models/update_template_body_variables_item.py +107 -0
- sendandretain/models/update_template_body_variables_item_type.py +12 -0
- sendandretain/models/update_template_meta_body.py +102 -0
- sendandretain/models/update_template_meta_response_200.py +81 -0
- sendandretain/models/update_template_response_200.py +87 -0
- sendandretain/models/update_template_response_200_status.py +8 -0
- sendandretain/models/update_webhook_endpoint_body.py +102 -0
- sendandretain/models/update_webhook_endpoint_response_200.py +294 -0
- sendandretain/models/upsert_contact_body.py +107 -0
- sendandretain/models/upsert_contact_body_attributes.py +47 -0
- sendandretain/models/verify_domain_response_200.py +161 -0
- sendandretain/models/verify_domain_response_200_dmarc.py +110 -0
- sendandretain/models/verify_domain_response_200_dmarc_recommended_record.py +89 -0
- sendandretain/models/verify_domain_response_200_dns_records_item.py +47 -0
- sendandretain/models/verify_domain_response_200_tracking_type_0.py +115 -0
- sendandretain/models/webhook_event_email_bounced.py +112 -0
- sendandretain/models/webhook_event_email_bounced_data.py +112 -0
- sendandretain/models/webhook_event_email_bounced_data_detail.py +108 -0
- sendandretain/models/webhook_event_email_bounced_type.py +8 -0
- sendandretain/models/webhook_event_email_bounced_version.py +8 -0
- sendandretain/models/webhook_event_email_canceled.py +112 -0
- sendandretain/models/webhook_event_email_canceled_data.py +112 -0
- sendandretain/models/webhook_event_email_canceled_data_detail.py +47 -0
- sendandretain/models/webhook_event_email_canceled_type.py +8 -0
- sendandretain/models/webhook_event_email_canceled_version.py +8 -0
- sendandretain/models/webhook_event_email_clicked.py +112 -0
- sendandretain/models/webhook_event_email_clicked_data.py +112 -0
- sendandretain/models/webhook_event_email_clicked_data_detail.py +108 -0
- sendandretain/models/webhook_event_email_clicked_type.py +8 -0
- sendandretain/models/webhook_event_email_clicked_version.py +8 -0
- sendandretain/models/webhook_event_email_complained.py +112 -0
- sendandretain/models/webhook_event_email_complained_data.py +112 -0
- sendandretain/models/webhook_event_email_complained_data_detail.py +72 -0
- sendandretain/models/webhook_event_email_complained_type.py +8 -0
- sendandretain/models/webhook_event_email_complained_version.py +8 -0
- sendandretain/models/webhook_event_email_delivered.py +112 -0
- sendandretain/models/webhook_event_email_delivered_data.py +112 -0
- sendandretain/models/webhook_event_email_delivered_data_detail.py +47 -0
- sendandretain/models/webhook_event_email_delivered_type.py +8 -0
- sendandretain/models/webhook_event_email_delivered_version.py +8 -0
- sendandretain/models/webhook_event_email_delivery_delayed.py +112 -0
- sendandretain/models/webhook_event_email_delivery_delayed_data.py +112 -0
- sendandretain/models/webhook_event_email_delivery_delayed_data_detail.py +72 -0
- sendandretain/models/webhook_event_email_delivery_delayed_type.py +8 -0
- sendandretain/models/webhook_event_email_delivery_delayed_version.py +8 -0
- sendandretain/models/webhook_event_email_failed.py +112 -0
- sendandretain/models/webhook_event_email_failed_data.py +112 -0
- sendandretain/models/webhook_event_email_failed_data_detail.py +90 -0
- sendandretain/models/webhook_event_email_failed_type.py +8 -0
- sendandretain/models/webhook_event_email_failed_version.py +8 -0
- sendandretain/models/webhook_event_email_opened.py +112 -0
- sendandretain/models/webhook_event_email_opened_data.py +112 -0
- sendandretain/models/webhook_event_email_opened_data_detail.py +90 -0
- sendandretain/models/webhook_event_email_opened_type.py +8 -0
- sendandretain/models/webhook_event_email_opened_version.py +8 -0
- sendandretain/models/webhook_event_email_sent.py +112 -0
- sendandretain/models/webhook_event_email_sent_data.py +112 -0
- sendandretain/models/webhook_event_email_sent_data_detail.py +47 -0
- sendandretain/models/webhook_event_email_sent_type.py +8 -0
- sendandretain/models/webhook_event_email_sent_version.py +8 -0
- sendandretain/models/webhook_event_email_suppressed.py +112 -0
- sendandretain/models/webhook_event_email_suppressed_data.py +112 -0
- sendandretain/models/webhook_event_email_suppressed_data_detail.py +72 -0
- sendandretain/models/webhook_event_email_suppressed_type.py +8 -0
- sendandretain/models/webhook_event_email_suppressed_version.py +8 -0
- sendandretain/models/webhook_event_email_unsubscribed.py +112 -0
- sendandretain/models/webhook_event_email_unsubscribed_data.py +112 -0
- sendandretain/models/webhook_event_email_unsubscribed_data_detail.py +72 -0
- sendandretain/models/webhook_event_email_unsubscribed_type.py +8 -0
- sendandretain/models/webhook_event_email_unsubscribed_version.py +8 -0
- sendandretain/models/webhook_event_webhook_test.py +112 -0
- sendandretain/models/webhook_event_webhook_test_data.py +91 -0
- sendandretain/models/webhook_event_webhook_test_data_detail.py +47 -0
- sendandretain/models/webhook_event_webhook_test_type.py +8 -0
- sendandretain/models/webhook_event_webhook_test_version.py +8 -0
- sendandretain/models/webhook_message.py +220 -0
- sendandretain/models/webhook_message_tags_type_0_item.py +69 -0
- sendandretain/py.typed +0 -0
- sendandretain/types.py +54 -0
- sendandretain-0.2.0.dist-info/METADATA +267 -0
- sendandretain-0.2.0.dist-info/RECORD +495 -0
- sendandretain-0.2.0.dist-info/WHEEL +4 -0
- sendandretain-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Send & Retain — Python SDK.
|
|
2
|
+
|
|
3
|
+
>>> from sendandretain import SendAndRetain
|
|
4
|
+
>>> client = SendAndRetain() # reads SENDANDRETAIN_API_KEY # doctest: +SKIP
|
|
5
|
+
>>> email = client.emails.send(to="jane@acme.com", template="welcome") # doctest: +SKIP
|
|
6
|
+
|
|
7
|
+
Three layers, top to bottom:
|
|
8
|
+
|
|
9
|
+
- `SendAndRetain` / `AsyncSendAndRetain` — one method per operation, typed
|
|
10
|
+
keyword arguments, typed exceptions. Generated from `openapi.json` by
|
|
11
|
+
`scripts/generate_facade.py` into `_resources.py`.
|
|
12
|
+
- `sendandretain.api` / `sendandretain.models` — the full generated client, for
|
|
13
|
+
anything the facade does not shape the way you need.
|
|
14
|
+
- `_runtime.py` — the transport (retries, idempotency, User-Agent), synced from
|
|
15
|
+
the Send & Retain source tree.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from . import api, errors, models, types
|
|
21
|
+
from ._client import AsyncSendAndRetain, SendAndRetain
|
|
22
|
+
from ._convenience import API_KEY_ENV_VAR, API_KEY_PREFIX, DEFAULT_BASE_URL, Client
|
|
23
|
+
from ._exceptions import (
|
|
24
|
+
APIConnectionError,
|
|
25
|
+
APIError,
|
|
26
|
+
AuthenticationError,
|
|
27
|
+
ConflictError,
|
|
28
|
+
InvalidRequestError,
|
|
29
|
+
NotFoundError,
|
|
30
|
+
PermissionDeniedError,
|
|
31
|
+
QuotaExceededError,
|
|
32
|
+
RateLimitError,
|
|
33
|
+
SendAndRetainError,
|
|
34
|
+
WebhookVerificationError,
|
|
35
|
+
)
|
|
36
|
+
from ._version import __version__
|
|
37
|
+
from ._webhooks import verify_webhook
|
|
38
|
+
from .client import AuthenticatedClient
|
|
39
|
+
from .client import Client as RawClient
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"API_KEY_ENV_VAR",
|
|
43
|
+
"API_KEY_PREFIX",
|
|
44
|
+
"DEFAULT_BASE_URL",
|
|
45
|
+
"APIConnectionError",
|
|
46
|
+
"APIError",
|
|
47
|
+
"AsyncSendAndRetain",
|
|
48
|
+
"AuthenticatedClient",
|
|
49
|
+
"AuthenticationError",
|
|
50
|
+
"Client",
|
|
51
|
+
"ConflictError",
|
|
52
|
+
"InvalidRequestError",
|
|
53
|
+
"NotFoundError",
|
|
54
|
+
"PermissionDeniedError",
|
|
55
|
+
"QuotaExceededError",
|
|
56
|
+
"RateLimitError",
|
|
57
|
+
"RawClient",
|
|
58
|
+
"SendAndRetain",
|
|
59
|
+
"SendAndRetainError",
|
|
60
|
+
"WebhookVerificationError",
|
|
61
|
+
"__version__",
|
|
62
|
+
"api",
|
|
63
|
+
"errors",
|
|
64
|
+
"models",
|
|
65
|
+
"types",
|
|
66
|
+
"verify_webhook",
|
|
67
|
+
]
|
sendandretain/_base.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Plumbing shared by every generated resource method in `_resources.py`.
|
|
2
|
+
|
|
3
|
+
A facade method hands its operation's generated module and plain Python values
|
|
4
|
+
to `_call`; this turns the body into the generated model, invokes the module's
|
|
5
|
+
`sync_detailed` / `asyncio_detailed`, and returns the parsed success model or
|
|
6
|
+
raises a typed `SendAndRetainError`. The per-operation knowledge — which
|
|
7
|
+
arguments are path, query or body — lives in the generated method signatures.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import inspect
|
|
13
|
+
import json
|
|
14
|
+
import types
|
|
15
|
+
import typing
|
|
16
|
+
from functools import cache
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
from ._exceptions import APIConnectionError, error_for
|
|
22
|
+
|
|
23
|
+
__all__ = ["AsyncResource", "Resource", "invoke", "ainvoke"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@cache
|
|
27
|
+
def _body_model(module: types.ModuleType) -> Any:
|
|
28
|
+
"""The attrs model the module's `body` parameter takes, or None."""
|
|
29
|
+
param = inspect.signature(module.sync_detailed).parameters.get("body")
|
|
30
|
+
if param is None:
|
|
31
|
+
return None
|
|
32
|
+
hint = typing.get_type_hints(module.sync_detailed).get("body", param.annotation)
|
|
33
|
+
for candidate in (hint, *typing.get_args(hint)):
|
|
34
|
+
if hasattr(candidate, "from_dict"):
|
|
35
|
+
return candidate
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _kwargs(module: types.ModuleType, body: dict[str, Any] | None, params: dict[str, Any]) -> dict[str, Any]:
|
|
40
|
+
kwargs = {k: v for k, v in params.items() if v is not None}
|
|
41
|
+
if body is not None:
|
|
42
|
+
model = _body_model(module)
|
|
43
|
+
compact = {k: v for k, v in body.items() if v is not None}
|
|
44
|
+
kwargs["body"] = model.from_dict(compact) if model is not None else compact
|
|
45
|
+
return kwargs
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _result(response: Any) -> Any:
|
|
49
|
+
status = int(response.status_code)
|
|
50
|
+
if 200 <= status < 300:
|
|
51
|
+
if response.parsed is not None:
|
|
52
|
+
return response.parsed
|
|
53
|
+
try:
|
|
54
|
+
return json.loads(response.content) if response.content else None
|
|
55
|
+
except ValueError:
|
|
56
|
+
return None
|
|
57
|
+
try:
|
|
58
|
+
payload = json.loads(response.content) if response.content else None
|
|
59
|
+
except ValueError:
|
|
60
|
+
payload = None
|
|
61
|
+
raise error_for(status, payload, response.headers.get("X-Request-Id"))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def invoke(raw: Any, module: types.ModuleType, body: dict[str, Any] | None, params: dict[str, Any]) -> Any:
|
|
65
|
+
try:
|
|
66
|
+
response = module.sync_detailed(client=raw, **_kwargs(module, body, params))
|
|
67
|
+
except httpx.TransportError as exc:
|
|
68
|
+
raise APIConnectionError(str(exc) or type(exc).__name__, code="network_error") from exc
|
|
69
|
+
return _result(response)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def ainvoke(raw: Any, module: types.ModuleType, body: dict[str, Any] | None, params: dict[str, Any]) -> Any:
|
|
73
|
+
try:
|
|
74
|
+
response = await module.asyncio_detailed(client=raw, **_kwargs(module, body, params))
|
|
75
|
+
except httpx.TransportError as exc:
|
|
76
|
+
raise APIConnectionError(str(exc) or type(exc).__name__, code="network_error") from exc
|
|
77
|
+
return _result(response)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Resource:
|
|
81
|
+
def __init__(self, client: Any) -> None:
|
|
82
|
+
self._raw = client
|
|
83
|
+
|
|
84
|
+
def _call(self, module: types.ModuleType, *, body: dict[str, Any] | None = None, **params: Any) -> Any:
|
|
85
|
+
return invoke(self._raw, module, body, params)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AsyncResource:
|
|
89
|
+
def __init__(self, client: Any) -> None:
|
|
90
|
+
self._raw = client
|
|
91
|
+
|
|
92
|
+
async def _call(self, module: types.ModuleType, *, body: dict[str, Any] | None = None, **params: Any) -> Any:
|
|
93
|
+
return await ainvoke(self._raw, module, body, params)
|
sendandretain/_client.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""`SendAndRetain` and `AsyncSendAndRetain` — the clients most code should use."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from ._resources import AsyncResources, Resources
|
|
10
|
+
from ._runtime import (
|
|
11
|
+
DEFAULT_MAX_RETRIES,
|
|
12
|
+
DEFAULT_TIMEOUT_SECONDS,
|
|
13
|
+
AsyncRetryTransport,
|
|
14
|
+
RetryTransport,
|
|
15
|
+
resolve_api_key,
|
|
16
|
+
resolve_base_url,
|
|
17
|
+
)
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
from ._webhooks import verify_webhook
|
|
20
|
+
from .client import AuthenticatedClient
|
|
21
|
+
|
|
22
|
+
__all__ = ["AsyncSendAndRetain", "SendAndRetain"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _raw_client(key: str, base_url: str, timeout: float, transport: Any) -> AuthenticatedClient:
|
|
26
|
+
return AuthenticatedClient(
|
|
27
|
+
base_url=base_url,
|
|
28
|
+
token=key,
|
|
29
|
+
prefix="Bearer",
|
|
30
|
+
timeout=httpx.Timeout(timeout),
|
|
31
|
+
httpx_args={"transport": transport},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SendAndRetain(Resources):
|
|
36
|
+
"""The Send & Retain client.
|
|
37
|
+
|
|
38
|
+
>>> from sendandretain import SendAndRetain
|
|
39
|
+
>>> client = SendAndRetain() # reads SENDANDRETAIN_API_KEY # doctest: +SKIP
|
|
40
|
+
>>> email = client.emails.send(to="jane@acme.com", template="welcome") # doctest: +SKIP
|
|
41
|
+
>>> email.id # doctest: +SKIP
|
|
42
|
+
|
|
43
|
+
Methods return the parsed model and raise a `SendAndRetainError` subclass on
|
|
44
|
+
any failure. Requests that fail with 408/429/5xx or a network error are
|
|
45
|
+
retried with backoff; every POST carries an `Idempotency-Key` (yours, or a
|
|
46
|
+
generated one) so a retry can never do the work twice.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
api_key: str | None = None,
|
|
52
|
+
*,
|
|
53
|
+
base_url: str | None = None,
|
|
54
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
55
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
56
|
+
transport: httpx.BaseTransport | None = None,
|
|
57
|
+
) -> None:
|
|
58
|
+
key = resolve_api_key(api_key)
|
|
59
|
+
retry = RetryTransport(key, __version__, max_retries=max_retries, transport=transport)
|
|
60
|
+
#: The generated client, for `sendandretain.api.*` functions directly.
|
|
61
|
+
self.raw = _raw_client(key, resolve_base_url(base_url), timeout, retry)
|
|
62
|
+
super().__init__(self.raw)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def verify_webhook(payload: str | bytes, headers: Any, secret: str) -> dict[str, Any]:
|
|
66
|
+
"""Verify a webhook delivery and return the event. See `sendandretain.verify_webhook`."""
|
|
67
|
+
return verify_webhook(payload, headers, secret)
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
self.raw.get_httpx_client().close()
|
|
71
|
+
|
|
72
|
+
def __enter__(self) -> SendAndRetain:
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __exit__(self, *_: object) -> None:
|
|
76
|
+
self.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AsyncSendAndRetain(AsyncResources):
|
|
80
|
+
"""The asyncio twin of `SendAndRetain`: same resources, every method awaitable.
|
|
81
|
+
|
|
82
|
+
>>> async with AsyncSendAndRetain() as client: # doctest: +SKIP
|
|
83
|
+
... email = await client.emails.send(to="jane@acme.com", template="welcome")
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
api_key: str | None = None,
|
|
89
|
+
*,
|
|
90
|
+
base_url: str | None = None,
|
|
91
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
92
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
93
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
key = resolve_api_key(api_key)
|
|
96
|
+
retry = AsyncRetryTransport(key, __version__, max_retries=max_retries, transport=transport)
|
|
97
|
+
self.raw = _raw_client(key, resolve_base_url(base_url), timeout, retry)
|
|
98
|
+
super().__init__(self.raw)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def verify_webhook(payload: str | bytes, headers: Any, secret: str) -> dict[str, Any]:
|
|
102
|
+
"""Verify a webhook delivery and return the event. Synchronous — it does no I/O."""
|
|
103
|
+
return verify_webhook(payload, headers, secret)
|
|
104
|
+
|
|
105
|
+
async def close(self) -> None:
|
|
106
|
+
await self.raw.get_async_httpx_client().aclose()
|
|
107
|
+
|
|
108
|
+
async def __aenter__(self) -> AsyncSendAndRetain:
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
async def __aexit__(self, *_: object) -> None:
|
|
112
|
+
await self.close()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""The hand-written convenience layer over the generated client.
|
|
2
|
+
|
|
3
|
+
Deliberately thin, and one of only two hand-written modules in the package.
|
|
4
|
+
Everything beside it under `sendandretain/` is generated from `openapi.json` and is the
|
|
5
|
+
contract; this module exists only so callers need not remember the base URL,
|
|
6
|
+
spell `AuthenticatedClient`, or wire up the `Bearer` prefix by hand.
|
|
7
|
+
|
|
8
|
+
Kept out of `__init__.py` so that regeneration, which rewrites `client.py`,
|
|
9
|
+
`api/`, `models/`, `types.py` and `errors.py` wholesale, can never clobber it.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from .client import AuthenticatedClient
|
|
19
|
+
|
|
20
|
+
__all__ = ["API_KEY_ENV_VAR", "API_KEY_PREFIX", "DEFAULT_BASE_URL", "Client"]
|
|
21
|
+
|
|
22
|
+
DEFAULT_BASE_URL = "https://sendandretain.com"
|
|
23
|
+
"""Production base URL for the Send & Retain API."""
|
|
24
|
+
|
|
25
|
+
API_KEY_PREFIX = "aem_"
|
|
26
|
+
"""Every Send & Retain API key starts with this. The server rejects anything else
|
|
27
|
+
before it touches the database, so checking it here turns a 401 into a
|
|
28
|
+
`ValueError` that names the actual problem."""
|
|
29
|
+
|
|
30
|
+
API_KEY_ENV_VAR = "SENDANDRETAIN_API_KEY"
|
|
31
|
+
"""Consulted when `api_key` is not passed explicitly."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def Client( # noqa: N802 - a factory that reads as a constructor at the call site
|
|
35
|
+
api_key: str | None = None,
|
|
36
|
+
*,
|
|
37
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
38
|
+
timeout: httpx.Timeout | float | None = None,
|
|
39
|
+
raise_on_unexpected_status: bool = False,
|
|
40
|
+
**kwargs: object,
|
|
41
|
+
) -> AuthenticatedClient:
|
|
42
|
+
"""Build an authenticated client for the Send & Retain API.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
api_key: A `aem_…` key. Falls back to `$SENDANDRETAIN_API_KEY`.
|
|
46
|
+
base_url: Override only to target a non-production deployment.
|
|
47
|
+
timeout: Passed through to httpx. Seconds, or an `httpx.Timeout`.
|
|
48
|
+
raise_on_unexpected_status: Raise `errors.UnexpectedStatus` on a status
|
|
49
|
+
the spec does not document, instead of returning `None`.
|
|
50
|
+
**kwargs: Forwarded to the generated `AuthenticatedClient` — `headers`,
|
|
51
|
+
`cookies`, `verify_ssl`, `follow_redirects`, `httpx_args`.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
An `AuthenticatedClient` accepted by every function in
|
|
55
|
+
`sendandretain.api.*` as the `client=` argument.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: No key was supplied, or it does not look like a Send & Retain key.
|
|
59
|
+
"""
|
|
60
|
+
key = api_key if api_key is not None else os.environ.get(API_KEY_ENV_VAR)
|
|
61
|
+
if not key:
|
|
62
|
+
raise ValueError(f"No API key. Pass Client(api_key=...) or set ${API_KEY_ENV_VAR}.")
|
|
63
|
+
if not key.startswith(API_KEY_PREFIX):
|
|
64
|
+
raise ValueError(f"Send & Retain API keys start with {API_KEY_PREFIX!r}; got {key[:8]!r}…")
|
|
65
|
+
|
|
66
|
+
if isinstance(timeout, (int, float)):
|
|
67
|
+
timeout = httpx.Timeout(timeout)
|
|
68
|
+
|
|
69
|
+
return AuthenticatedClient(
|
|
70
|
+
base_url=base_url,
|
|
71
|
+
token=key,
|
|
72
|
+
prefix="Bearer",
|
|
73
|
+
timeout=timeout,
|
|
74
|
+
raise_on_unexpected_status=raise_on_unexpected_status,
|
|
75
|
+
**kwargs, # type: ignore[arg-type]
|
|
76
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Typed exceptions. Every API error raised by the facade is a `SendAndRetainError`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"APIConnectionError",
|
|
9
|
+
"APIError",
|
|
10
|
+
"AuthenticationError",
|
|
11
|
+
"ConflictError",
|
|
12
|
+
"InvalidRequestError",
|
|
13
|
+
"NotFoundError",
|
|
14
|
+
"PermissionDeniedError",
|
|
15
|
+
"QuotaExceededError",
|
|
16
|
+
"RateLimitError",
|
|
17
|
+
"SendAndRetainError",
|
|
18
|
+
"WebhookVerificationError",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SendAndRetainError(Exception):
|
|
23
|
+
"""Base class. Branch on `code`; quote `request_id` to support."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
message: str,
|
|
28
|
+
*,
|
|
29
|
+
code: str,
|
|
30
|
+
status: int | None = None,
|
|
31
|
+
request_id: str | None = None,
|
|
32
|
+
body: dict[str, Any] | None = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
self.message = message
|
|
36
|
+
self.code = code
|
|
37
|
+
self.status = status
|
|
38
|
+
self.request_id = request_id
|
|
39
|
+
#: The full `error` object, including fields like `required_scope`.
|
|
40
|
+
self.body = body or {}
|
|
41
|
+
|
|
42
|
+
def __str__(self) -> str:
|
|
43
|
+
ref = f" (request_id {self.request_id})" if self.request_id else ""
|
|
44
|
+
return f"{self.code}: {self.message}{ref}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class APIError(SendAndRetainError):
|
|
48
|
+
"""Any error response not covered by a more specific class — 5xx included."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class InvalidRequestError(APIError):
|
|
52
|
+
"""400 / 422: the request itself is wrong. Nothing changed; fix and resend."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AuthenticationError(APIError):
|
|
56
|
+
"""401: missing, invalid, revoked or expired key."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PermissionDeniedError(APIError):
|
|
60
|
+
"""403: the key is valid but lacks the scope or the send grant. See `body["required_scope"]`."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class NotFoundError(APIError):
|
|
64
|
+
"""404: no such resource in this project."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ConflictError(APIError):
|
|
68
|
+
"""409: an idempotency conflict, or the resource's state forbids the action."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RateLimitError(APIError):
|
|
72
|
+
"""429 `rate_limited`, after the SDK's own retries were exhausted."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class QuotaExceededError(APIError):
|
|
76
|
+
"""429 `daily_cap` / `monthly_cap`: a sending quota, not a throttle. Never retried."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class APIConnectionError(SendAndRetainError):
|
|
80
|
+
"""No response arrived: DNS, TLS, a reset connection, or a timeout."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class WebhookVerificationError(SendAndRetainError):
|
|
84
|
+
"""A webhook's signature or timestamp did not verify. Answer 400 and do nothing else."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def error_for(status: int, payload: Any, request_id: str | None) -> APIError:
|
|
88
|
+
envelope = payload.get("error") if isinstance(payload, dict) else None
|
|
89
|
+
envelope = envelope if isinstance(envelope, dict) else {}
|
|
90
|
+
code = str(envelope.get("code") or f"http_{status}")
|
|
91
|
+
message = str(envelope.get("message") or f"HTTP {status}")
|
|
92
|
+
rid = envelope.get("request_id") or request_id
|
|
93
|
+
cls: type[APIError]
|
|
94
|
+
if code in ("daily_cap", "monthly_cap"):
|
|
95
|
+
cls = QuotaExceededError
|
|
96
|
+
elif status in (400, 422):
|
|
97
|
+
cls = InvalidRequestError
|
|
98
|
+
else:
|
|
99
|
+
cls = {
|
|
100
|
+
401: AuthenticationError,
|
|
101
|
+
403: PermissionDeniedError,
|
|
102
|
+
404: NotFoundError,
|
|
103
|
+
409: ConflictError,
|
|
104
|
+
429: RateLimitError,
|
|
105
|
+
}.get(status, APIError)
|
|
106
|
+
return cls(message, code=code, status=status, request_id=rid, body=envelope)
|