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.
Files changed (495) hide show
  1. sendandretain/__init__.py +67 -0
  2. sendandretain/_base.py +93 -0
  3. sendandretain/_client.py +112 -0
  4. sendandretain/_convenience.py +76 -0
  5. sendandretain/_exceptions.py +106 -0
  6. sendandretain/_resources.py +984 -0
  7. sendandretain/_runtime.py +295 -0
  8. sendandretain/_version.py +3 -0
  9. sendandretain/_webhooks.py +78 -0
  10. sendandretain/api/__init__.py +1 -0
  11. sendandretain/api/audience/__init__.py +1 -0
  12. sendandretain/api/audience/create_segment.py +213 -0
  13. sendandretain/api/audience/delete_segment.py +176 -0
  14. sendandretain/api/audience/list_segments.py +149 -0
  15. sendandretain/api/audience/refresh_segment_count.py +188 -0
  16. sendandretain/api/audience/update_segment.py +202 -0
  17. sendandretain/api/automations/__init__.py +1 -0
  18. sendandretain/api/automations/add_automation_steps.py +202 -0
  19. sendandretain/api/automations/archive_automation.py +184 -0
  20. sendandretain/api/automations/backfill_missed_enrollments.py +243 -0
  21. sendandretain/api/automations/create_automation.py +221 -0
  22. sendandretain/api/automations/duplicate_automation.py +211 -0
  23. sendandretain/api/automations/get_automation.py +189 -0
  24. sendandretain/api/automations/get_automation_metrics.py +222 -0
  25. sendandretain/api/automations/list_automation_runs.py +240 -0
  26. sendandretain/api/automations/list_automations.py +157 -0
  27. sendandretain/api/automations/move_automation_step.py +198 -0
  28. sendandretain/api/automations/preflight_automation.py +197 -0
  29. sendandretain/api/automations/promote_ab_winner.py +218 -0
  30. sendandretain/api/automations/remove_automation_step.py +198 -0
  31. sendandretain/api/automations/set_ab_test.py +214 -0
  32. sendandretain/api/automations/set_automation_status.py +227 -0
  33. sendandretain/api/automations/sync_automation_step_props.py +202 -0
  34. sendandretain/api/automations/update_automation.py +218 -0
  35. sendandretain/api/automations/update_automation_step.py +202 -0
  36. sendandretain/api/contacts/__init__.py +1 -0
  37. sendandretain/api/contacts/delete_contact.py +188 -0
  38. sendandretain/api/contacts/get_contact.py +184 -0
  39. sendandretain/api/contacts/import_contacts.py +221 -0
  40. sendandretain/api/contacts/list_contact_events.py +244 -0
  41. sendandretain/api/contacts/list_contacts.py +233 -0
  42. sendandretain/api/contacts/upsert_contact.py +192 -0
  43. sendandretain/api/emails/__init__.py +1 -0
  44. sendandretain/api/emails/cancel_email.py +188 -0
  45. sendandretain/api/emails/get_message.py +182 -0
  46. sendandretain/api/emails/list_emails.py +253 -0
  47. sendandretain/api/emails/reschedule_email.py +209 -0
  48. sendandretain/api/emails/send_email.py +273 -0
  49. sendandretain/api/emails/send_email_batch.py +263 -0
  50. sendandretain/api/events/__init__.py +1 -0
  51. sendandretain/api/events/emit_event.py +224 -0
  52. sendandretain/api/metrics/__init__.py +1 -0
  53. sendandretain/api/metrics/get_metrics.py +219 -0
  54. sendandretain/api/metrics/get_metrics_trends.py +197 -0
  55. sendandretain/api/metrics/get_queue_health.py +165 -0
  56. sendandretain/api/settings/__init__.py +1 -0
  57. sendandretain/api/settings/get_brand.py +165 -0
  58. sendandretain/api/settings/get_project_settings.py +161 -0
  59. sendandretain/api/settings/update_brand.py +193 -0
  60. sendandretain/api/settings/update_project_settings.py +209 -0
  61. sendandretain/api/setup/__init__.py +1 -0
  62. sendandretain/api/setup/create_domain.py +225 -0
  63. sendandretain/api/setup/create_sender.py +213 -0
  64. sendandretain/api/setup/delete_domain.py +205 -0
  65. sendandretain/api/setup/delete_sender.py +197 -0
  66. sendandretain/api/setup/get_connection_status.py +173 -0
  67. sendandretain/api/setup/get_onboarding_steps.py +181 -0
  68. sendandretain/api/setup/list_domains.py +149 -0
  69. sendandretain/api/setup/list_senders.py +149 -0
  70. sendandretain/api/setup/register_provider_webhook.py +177 -0
  71. sendandretain/api/setup/register_webhooks.py +169 -0
  72. sendandretain/api/setup/update_sender.py +218 -0
  73. sendandretain/api/setup/verify_domain.py +192 -0
  74. sendandretain/api/suppressions/__init__.py +1 -0
  75. sendandretain/api/suppressions/add_suppression.py +209 -0
  76. sendandretain/api/suppressions/import_suppressions.py +217 -0
  77. sendandretain/api/suppressions/list_suppressions.py +219 -0
  78. sendandretain/api/suppressions/remove_suppression.py +193 -0
  79. sendandretain/api/templates/__init__.py +1 -0
  80. sendandretain/api/templates/add_template_translation.py +214 -0
  81. sendandretain/api/templates/archive_template.py +193 -0
  82. sendandretain/api/templates/create_template.py +213 -0
  83. sendandretain/api/templates/get_template.py +210 -0
  84. sendandretain/api/templates/list_template_versions.py +189 -0
  85. sendandretain/api/templates/list_templates.py +157 -0
  86. sendandretain/api/templates/publish_template_version.py +219 -0
  87. sendandretain/api/templates/render_template.py +219 -0
  88. sendandretain/api/templates/send_template_test.py +218 -0
  89. sendandretain/api/templates/unpublish_template.py +205 -0
  90. sendandretain/api/templates/update_template.py +214 -0
  91. sendandretain/api/templates/update_template_meta.py +218 -0
  92. sendandretain/api/webhooks/__init__.py +1 -0
  93. sendandretain/api/webhooks/create_webhook_endpoint.py +225 -0
  94. sendandretain/api/webhooks/delete_webhook_endpoint.py +193 -0
  95. sendandretain/api/webhooks/get_webhook_endpoint.py +181 -0
  96. sendandretain/api/webhooks/list_webhook_deliveries.py +267 -0
  97. sendandretain/api/webhooks/list_webhook_endpoints.py +157 -0
  98. sendandretain/api/webhooks/replay_webhook_delivery.py +216 -0
  99. sendandretain/api/webhooks/rotate_webhook_secret.py +219 -0
  100. sendandretain/api/webhooks/test_webhook_endpoint.py +197 -0
  101. sendandretain/api/webhooks/update_webhook_endpoint.py +214 -0
  102. sendandretain/client.py +268 -0
  103. sendandretain/errors.py +16 -0
  104. sendandretain/models/__init__.py +819 -0
  105. sendandretain/models/add_automation_steps_body.py +262 -0
  106. sendandretain/models/add_automation_steps_response_200.py +70 -0
  107. sendandretain/models/add_automation_steps_schema_0_variant_0.py +146 -0
  108. sendandretain/models/add_automation_steps_schema_0_variant_0_props_overrides.py +47 -0
  109. sendandretain/models/add_automation_steps_schema_0_variant_0_send_window.py +91 -0
  110. sendandretain/models/add_automation_steps_schema_0_variant_1.py +52 -0
  111. sendandretain/models/add_automation_steps_schema_0_variant_2.py +114 -0
  112. sendandretain/models/add_automation_steps_schema_0_variant_2_send_window.py +91 -0
  113. sendandretain/models/add_automation_steps_schema_0_variant_3.py +140 -0
  114. sendandretain/models/add_automation_steps_schema_0_variant_3_attributes.py +47 -0
  115. sendandretain/models/add_automation_steps_schema_0_variant_3_send_window.py +91 -0
  116. sendandretain/models/add_automation_steps_schema_0_variant_4.py +124 -0
  117. sendandretain/models/add_automation_steps_schema_0_variant_4_method.py +9 -0
  118. sendandretain/models/add_automation_steps_schema_0_variant_4_send_window.py +91 -0
  119. sendandretain/models/add_automation_steps_schema_0_variant_5.py +83 -0
  120. sendandretain/models/add_automation_steps_schema_0_variant_6.py +71 -0
  121. sendandretain/models/add_automation_steps_schema_0_variant_7.py +712 -0
  122. sendandretain/models/add_automation_steps_schema_0_variant_7_branches_filter_variant_0_item.py +96 -0
  123. sendandretain/models/add_automation_steps_schema_0_variant_7_branches_filter_variant_0_item_op.py +17 -0
  124. sendandretain/models/add_automation_steps_schema_0_variant_7_branches_item.py +310 -0
  125. sendandretain/models/add_automation_steps_schema_0_variant_7_filter_variant_0_item.py +96 -0
  126. sendandretain/models/add_automation_steps_schema_0_variant_7_filter_variant_0_item_op.py +17 -0
  127. sendandretain/models/add_automation_steps_schema_0_variant_7_if_match.py +9 -0
  128. sendandretain/models/add_automation_steps_schema_0_variant_7_if_no_match.py +9 -0
  129. sendandretain/models/add_automation_steps_schema_0_variant_7_send_window.py +91 -0
  130. sendandretain/models/add_automation_steps_schema_1.py +114 -0
  131. sendandretain/models/add_automation_steps_schema_1_conditions_variant_0.py +92 -0
  132. sendandretain/models/add_automation_steps_schema_1_conditions_variant_0_op.py +17 -0
  133. sendandretain/models/add_automation_steps_schema_1_op.py +9 -0
  134. sendandretain/models/add_suppression_body.py +90 -0
  135. sendandretain/models/add_suppression_body_reason.py +11 -0
  136. sendandretain/models/add_suppression_response_201.py +70 -0
  137. sendandretain/models/add_template_translation_body.py +80 -0
  138. sendandretain/models/add_template_translation_response_201.py +96 -0
  139. sendandretain/models/add_template_translation_response_201_status.py +8 -0
  140. sendandretain/models/archive_automation_response_200.py +70 -0
  141. sendandretain/models/archive_template_response_200.py +70 -0
  142. sendandretain/models/automations_id_ab_test_post_request_body_content_application_json_variants_variant_0_item.py +81 -0
  143. sendandretain/models/automations_id_patch_request_body_content_application_json_trigger_filter_variant_0_variant_0_item.py +96 -0
  144. sendandretain/models/automations_id_patch_request_body_content_application_json_trigger_filter_variant_0_variant_0_item_op.py +17 -0
  145. sendandretain/models/automations_post_request_body_content_application_json_trigger_filter_variant_0_item.py +96 -0
  146. sendandretain/models/automations_post_request_body_content_application_json_trigger_filter_variant_0_item_op.py +17 -0
  147. sendandretain/models/automations_steps_put_request_body_content_application_json_patch_branches_filter_variant_0_item.py +96 -0
  148. sendandretain/models/automations_steps_put_request_body_content_application_json_patch_branches_filter_variant_0_item_op.py +17 -0
  149. sendandretain/models/automations_steps_put_request_body_content_application_json_patch_filter_variant_0_item.py +96 -0
  150. sendandretain/models/automations_steps_put_request_body_content_application_json_patch_filter_variant_0_item_op.py +17 -0
  151. sendandretain/models/backfill_missed_enrollments_body.py +79 -0
  152. sendandretain/models/backfill_missed_enrollments_response_200.py +115 -0
  153. sendandretain/models/cancel_email_response_200.py +78 -0
  154. sendandretain/models/cancel_email_response_200_status.py +8 -0
  155. sendandretain/models/contact_result.py +69 -0
  156. sendandretain/models/create_automation_body.py +378 -0
  157. sendandretain/models/create_automation_response_201.py +87 -0
  158. sendandretain/models/create_automation_response_201_status.py +8 -0
  159. sendandretain/models/create_automation_schema_0.py +110 -0
  160. sendandretain/models/create_automation_schema_0_conditions_variant_0.py +92 -0
  161. sendandretain/models/create_automation_schema_0_conditions_variant_0_op.py +17 -0
  162. sendandretain/models/create_automation_schema_0_op.py +9 -0
  163. sendandretain/models/create_automation_schema_1_variant_0.py +144 -0
  164. sendandretain/models/create_automation_schema_1_variant_0_props_overrides.py +47 -0
  165. sendandretain/models/create_automation_schema_1_variant_0_send_window.py +91 -0
  166. sendandretain/models/create_automation_schema_1_variant_1.py +52 -0
  167. sendandretain/models/create_automation_schema_1_variant_2.py +112 -0
  168. sendandretain/models/create_automation_schema_1_variant_2_send_window.py +91 -0
  169. sendandretain/models/create_automation_schema_1_variant_3.py +136 -0
  170. sendandretain/models/create_automation_schema_1_variant_3_attributes.py +47 -0
  171. sendandretain/models/create_automation_schema_1_variant_3_send_window.py +91 -0
  172. sendandretain/models/create_automation_schema_1_variant_4.py +121 -0
  173. sendandretain/models/create_automation_schema_1_variant_4_method.py +9 -0
  174. sendandretain/models/create_automation_schema_1_variant_4_send_window.py +91 -0
  175. sendandretain/models/create_automation_schema_1_variant_5.py +83 -0
  176. sendandretain/models/create_automation_schema_1_variant_6.py +71 -0
  177. sendandretain/models/create_automation_schema_1_variant_7.py +703 -0
  178. sendandretain/models/create_automation_schema_1_variant_7_branches_filter_variant_0_item.py +96 -0
  179. sendandretain/models/create_automation_schema_1_variant_7_branches_filter_variant_0_item_op.py +17 -0
  180. sendandretain/models/create_automation_schema_1_variant_7_branches_item.py +313 -0
  181. sendandretain/models/create_automation_schema_1_variant_7_filter_variant_0_item.py +96 -0
  182. sendandretain/models/create_automation_schema_1_variant_7_filter_variant_0_item_op.py +17 -0
  183. sendandretain/models/create_automation_schema_1_variant_7_if_match.py +9 -0
  184. sendandretain/models/create_automation_schema_1_variant_7_if_no_match.py +9 -0
  185. sendandretain/models/create_automation_schema_1_variant_7_send_window.py +91 -0
  186. sendandretain/models/create_domain_body.py +61 -0
  187. sendandretain/models/create_domain_response_201.py +161 -0
  188. sendandretain/models/create_domain_response_201_dmarc.py +110 -0
  189. sendandretain/models/create_domain_response_201_dmarc_recommended_record.py +89 -0
  190. sendandretain/models/create_domain_response_201_dns_records_item.py +99 -0
  191. sendandretain/models/create_domain_response_201_tracking_type_0.py +115 -0
  192. sendandretain/models/create_segment_body.py +105 -0
  193. sendandretain/models/create_segment_body_filter_item.py +81 -0
  194. sendandretain/models/create_segment_body_filter_item_op.py +17 -0
  195. sendandretain/models/create_segment_response_201.py +70 -0
  196. sendandretain/models/create_sender_body.py +100 -0
  197. sendandretain/models/create_sender_response_201.py +88 -0
  198. sendandretain/models/create_template_body.py +136 -0
  199. sendandretain/models/create_template_body_category.py +9 -0
  200. sendandretain/models/create_template_body_variables_item.py +107 -0
  201. sendandretain/models/create_template_body_variables_item_type.py +12 -0
  202. sendandretain/models/create_template_response_201.py +96 -0
  203. sendandretain/models/create_template_response_201_status.py +8 -0
  204. sendandretain/models/create_webhook_endpoint_body.py +93 -0
  205. sendandretain/models/create_webhook_endpoint_response_201.py +303 -0
  206. sendandretain/models/delete_contact_response_200.py +70 -0
  207. sendandretain/models/delete_domain_response_200.py +91 -0
  208. sendandretain/models/delete_segment_response_200.py +70 -0
  209. sendandretain/models/delete_sender_response_200.py +70 -0
  210. sendandretain/models/delete_webhook_endpoint_response_200.py +61 -0
  211. sendandretain/models/duplicate_automation_response_201.py +87 -0
  212. sendandretain/models/duplicate_automation_response_201_status.py +8 -0
  213. sendandretain/models/emit_event_body.py +151 -0
  214. sendandretain/models/emit_event_body_contact_attributes.py +47 -0
  215. sendandretain/models/emit_event_body_properties.py +47 -0
  216. sendandretain/models/emit_event_response_202.py +71 -0
  217. sendandretain/models/emit_event_response_202_status.py +8 -0
  218. sendandretain/models/error.py +68 -0
  219. sendandretain/models/error_error.py +143 -0
  220. sendandretain/models/error_error_code.py +24 -0
  221. sendandretain/models/error_error_key_scope.py +10 -0
  222. sendandretain/models/error_error_required_grant.py +8 -0
  223. sendandretain/models/error_error_required_scope.py +10 -0
  224. sendandretain/models/event_result.py +121 -0
  225. sendandretain/models/event_result_skipped_item.py +81 -0
  226. sendandretain/models/event_result_skipped_item_reason.py +13 -0
  227. sendandretain/models/get_automation_metrics_response_200.py +152 -0
  228. sendandretain/models/get_automation_metrics_response_200_ab_tests_item.py +132 -0
  229. sendandretain/models/get_automation_metrics_response_200_ab_tests_item_arms_item.py +88 -0
  230. sendandretain/models/get_automation_metrics_response_200_by_step.py +47 -0
  231. sendandretain/models/get_automation_metrics_response_200_by_variant.py +47 -0
  232. sendandretain/models/get_automation_metrics_response_200_run_counts.py +47 -0
  233. sendandretain/models/get_automation_response_200.py +215 -0
  234. sendandretain/models/get_automation_response_200_steps_item.py +203 -0
  235. sendandretain/models/get_automation_response_200_steps_item_config_type_0.py +47 -0
  236. sendandretain/models/get_brand_response_200.py +103 -0
  237. sendandretain/models/get_brand_response_200_brand.py +47 -0
  238. sendandretain/models/get_connection_status_response_200.py +188 -0
  239. sendandretain/models/get_connection_status_response_200_domains_item.py +47 -0
  240. sendandretain/models/get_connection_status_response_200_provider_type_0.py +99 -0
  241. sendandretain/models/get_connection_status_response_200_senders_item.py +47 -0
  242. sendandretain/models/get_contact_response_200.py +47 -0
  243. sendandretain/models/get_metrics_response_200.py +170 -0
  244. sendandretain/models/get_metrics_response_200_by_day.py +47 -0
  245. sendandretain/models/get_metrics_response_200_by_template.py +47 -0
  246. sendandretain/models/get_metrics_response_200_period.py +85 -0
  247. sendandretain/models/get_metrics_response_200_source.py +9 -0
  248. sendandretain/models/get_metrics_response_200_totals.py +47 -0
  249. sendandretain/models/get_metrics_trends_response_200.py +106 -0
  250. sendandretain/models/get_metrics_trends_response_200_series_item.py +135 -0
  251. sendandretain/models/get_metrics_trends_response_200_totals.py +47 -0
  252. sendandretain/models/get_onboarding_steps_response_200.py +148 -0
  253. sendandretain/models/get_onboarding_steps_response_200_steps_item.py +47 -0
  254. sendandretain/models/get_project_settings_response_200.py +146 -0
  255. sendandretain/models/get_project_settings_response_200_utm_type_0.py +47 -0
  256. sendandretain/models/get_queue_health_response_200.py +99 -0
  257. sendandretain/models/get_template_response_200.py +189 -0
  258. sendandretain/models/get_template_response_200_variables_item.py +47 -0
  259. sendandretain/models/get_webhook_endpoint_response_200.py +294 -0
  260. sendandretain/models/import_contacts_body.py +76 -0
  261. sendandretain/models/import_contacts_body_contacts_item.py +116 -0
  262. sendandretain/models/import_contacts_body_contacts_item_attributes.py +47 -0
  263. sendandretain/models/import_contacts_response_200.py +81 -0
  264. sendandretain/models/import_suppressions_body.py +76 -0
  265. sendandretain/models/import_suppressions_body_entries_item.py +89 -0
  266. sendandretain/models/import_suppressions_body_entries_item_reason.py +9 -0
  267. sendandretain/models/import_suppressions_response_200.py +81 -0
  268. sendandretain/models/list_automation_runs_response_200.py +107 -0
  269. sendandretain/models/list_automation_runs_response_200_data_item.py +305 -0
  270. sendandretain/models/list_automation_runs_response_200_data_item_variant_assignments.py +47 -0
  271. sendandretain/models/list_automation_runs_response_200_object.py +8 -0
  272. sendandretain/models/list_automations_response_200.py +107 -0
  273. sendandretain/models/list_automations_response_200_data_item.py +106 -0
  274. sendandretain/models/list_automations_response_200_object.py +8 -0
  275. sendandretain/models/list_contact_events_response_200.py +107 -0
  276. sendandretain/models/list_contact_events_response_200_data_item.py +149 -0
  277. sendandretain/models/list_contact_events_response_200_data_item_properties.py +47 -0
  278. sendandretain/models/list_contact_events_response_200_object.py +8 -0
  279. sendandretain/models/list_contacts_response_200.py +107 -0
  280. sendandretain/models/list_contacts_response_200_data_item.py +47 -0
  281. sendandretain/models/list_contacts_response_200_object.py +8 -0
  282. sendandretain/models/list_domains_response_200.py +107 -0
  283. sendandretain/models/list_domains_response_200_data_item.py +346 -0
  284. sendandretain/models/list_domains_response_200_data_item_dns_records_item.py +47 -0
  285. sendandretain/models/list_domains_response_200_object.py +8 -0
  286. sendandretain/models/list_emails_response_200.py +107 -0
  287. sendandretain/models/list_emails_response_200_object.py +8 -0
  288. sendandretain/models/list_envelope.py +94 -0
  289. sendandretain/models/list_envelope_object.py +8 -0
  290. sendandretain/models/list_segments_response_200.py +107 -0
  291. sendandretain/models/list_segments_response_200_data_item.py +200 -0
  292. sendandretain/models/list_segments_response_200_data_item_filter_item.py +47 -0
  293. sendandretain/models/list_segments_response_200_object.py +8 -0
  294. sendandretain/models/list_senders_response_200.py +107 -0
  295. sendandretain/models/list_senders_response_200_data_item.py +154 -0
  296. sendandretain/models/list_senders_response_200_object.py +8 -0
  297. sendandretain/models/list_suppressions_reason.py +12 -0
  298. sendandretain/models/list_suppressions_response_200.py +107 -0
  299. sendandretain/models/list_suppressions_response_200_data_item.py +47 -0
  300. sendandretain/models/list_suppressions_response_200_object.py +8 -0
  301. sendandretain/models/list_template_versions_response_200.py +107 -0
  302. sendandretain/models/list_template_versions_response_200_data_item.py +145 -0
  303. sendandretain/models/list_template_versions_response_200_object.py +8 -0
  304. sendandretain/models/list_templates_response_200.py +107 -0
  305. sendandretain/models/list_templates_response_200_data_item.py +154 -0
  306. sendandretain/models/list_templates_response_200_object.py +8 -0
  307. sendandretain/models/list_webhook_deliveries_response_200.py +107 -0
  308. sendandretain/models/list_webhook_deliveries_response_200_data_item.py +244 -0
  309. sendandretain/models/list_webhook_deliveries_response_200_object.py +8 -0
  310. sendandretain/models/list_webhook_endpoints_response_200.py +107 -0
  311. sendandretain/models/list_webhook_endpoints_response_200_data_item.py +294 -0
  312. sendandretain/models/list_webhook_endpoints_response_200_object.py +8 -0
  313. sendandretain/models/message_status.py +350 -0
  314. sendandretain/models/message_status_events_item.py +87 -0
  315. sendandretain/models/move_automation_step_body.py +97 -0
  316. sendandretain/models/move_automation_step_body_direction.py +9 -0
  317. sendandretain/models/move_automation_step_response_200.py +61 -0
  318. sendandretain/models/preflight_automation_response_200.py +88 -0
  319. sendandretain/models/preflight_automation_response_200_checks_item.py +88 -0
  320. sendandretain/models/promote_ab_winner_body.py +61 -0
  321. sendandretain/models/promote_ab_winner_response_200.py +70 -0
  322. sendandretain/models/publish_template_version_body.py +61 -0
  323. sendandretain/models/publish_template_version_response_200.py +99 -0
  324. sendandretain/models/refresh_segment_count_response_200.py +70 -0
  325. sendandretain/models/register_provider_webhook_response_200.py +70 -0
  326. sendandretain/models/register_webhooks_response_200.py +70 -0
  327. sendandretain/models/remove_automation_step_body.py +69 -0
  328. sendandretain/models/remove_automation_step_response_200.py +61 -0
  329. sendandretain/models/remove_suppression_response_200.py +70 -0
  330. sendandretain/models/render_template_body.py +84 -0
  331. sendandretain/models/render_template_body_props.py +47 -0
  332. sendandretain/models/render_template_response_200.py +120 -0
  333. sendandretain/models/render_template_response_200_props_used.py +47 -0
  334. sendandretain/models/replay_webhook_delivery_response_202.py +61 -0
  335. sendandretain/models/reschedule_email_body.py +61 -0
  336. sendandretain/models/reschedule_email_response_200.py +87 -0
  337. sendandretain/models/reschedule_email_response_200_status.py +8 -0
  338. sendandretain/models/rotate_webhook_secret_body.py +63 -0
  339. sendandretain/models/rotate_webhook_secret_response_200.py +78 -0
  340. sendandretain/models/send_email_batch_body.py +76 -0
  341. sendandretain/models/send_email_batch_body_emails_item.py +282 -0
  342. sendandretain/models/send_email_batch_body_emails_item_attachments_item.py +80 -0
  343. sendandretain/models/send_email_batch_body_emails_item_headers.py +47 -0
  344. sendandretain/models/send_email_batch_body_emails_item_props.py +47 -0
  345. sendandretain/models/send_email_batch_body_emails_item_tags_item.py +69 -0
  346. sendandretain/models/send_email_batch_response_202.py +97 -0
  347. sendandretain/models/send_email_batch_response_202_data_item.py +110 -0
  348. sendandretain/models/send_email_batch_response_202_data_item_error.py +71 -0
  349. sendandretain/models/send_email_body.py +279 -0
  350. sendandretain/models/send_email_body_attachments_item.py +80 -0
  351. sendandretain/models/send_email_body_headers.py +47 -0
  352. sendandretain/models/send_email_body_props.py +47 -0
  353. sendandretain/models/send_email_body_tags_item.py +69 -0
  354. sendandretain/models/send_result.py +82 -0
  355. sendandretain/models/send_result_status.py +10 -0
  356. sendandretain/models/send_template_test_body.py +94 -0
  357. sendandretain/models/send_template_test_body_props.py +47 -0
  358. sendandretain/models/send_template_test_response_201.py +79 -0
  359. sendandretain/models/set_ab_test_body.py +115 -0
  360. sendandretain/models/set_ab_test_response_200.py +92 -0
  361. sendandretain/models/set_automation_status_body.py +73 -0
  362. sendandretain/models/set_automation_status_body_status.py +9 -0
  363. sendandretain/models/set_automation_status_response_200.py +70 -0
  364. sendandretain/models/settings_patch_request_body_content_application_json_utm_variant_0.py +79 -0
  365. sendandretain/models/settings_patch_request_body_content_application_json_utm_variant_0_params.py +47 -0
  366. sendandretain/models/sync_automation_step_props_body.py +95 -0
  367. sendandretain/models/sync_automation_step_props_body_updates_item.py +79 -0
  368. sendandretain/models/sync_automation_step_props_body_updates_item_props_overrides.py +47 -0
  369. sendandretain/models/sync_automation_step_props_response_200.py +72 -0
  370. sendandretain/models/test_webhook_endpoint_response_202.py +61 -0
  371. sendandretain/models/unpublish_template_response_200.py +70 -0
  372. sendandretain/models/update_automation_body.py +247 -0
  373. sendandretain/models/update_automation_response_200.py +61 -0
  374. sendandretain/models/update_automation_schema_0.py +110 -0
  375. sendandretain/models/update_automation_schema_0_conditions_variant_0.py +92 -0
  376. sendandretain/models/update_automation_schema_0_conditions_variant_0_op.py +17 -0
  377. sendandretain/models/update_automation_schema_0_op.py +9 -0
  378. sendandretain/models/update_automation_step_body.py +83 -0
  379. sendandretain/models/update_automation_step_body_patch.py +390 -0
  380. sendandretain/models/update_automation_step_body_patch_attributes.py +47 -0
  381. sendandretain/models/update_automation_step_body_patch_branches_item.py +134 -0
  382. sendandretain/models/update_automation_step_body_patch_if_match.py +9 -0
  383. sendandretain/models/update_automation_step_body_patch_if_no_match.py +9 -0
  384. sendandretain/models/update_automation_step_body_patch_method.py +9 -0
  385. sendandretain/models/update_automation_step_body_patch_props_overrides.py +47 -0
  386. sendandretain/models/update_automation_step_body_patch_send_window.py +91 -0
  387. sendandretain/models/update_automation_step_response_200.py +61 -0
  388. sendandretain/models/update_brand_body.py +68 -0
  389. sendandretain/models/update_brand_body_brand.py +170 -0
  390. sendandretain/models/update_brand_body_brand_colors.py +97 -0
  391. sendandretain/models/update_brand_body_brand_fonts.py +70 -0
  392. sendandretain/models/update_brand_body_brand_logo.py +70 -0
  393. sendandretain/models/update_brand_body_brand_social_links_item.py +71 -0
  394. sendandretain/models/update_brand_body_brand_social_links_item_platform.py +15 -0
  395. sendandretain/models/update_brand_body_brand_voice.py +72 -0
  396. sendandretain/models/update_brand_response_200.py +83 -0
  397. sendandretain/models/update_brand_response_200_brand.py +47 -0
  398. sendandretain/models/update_project_settings_body.py +149 -0
  399. sendandretain/models/update_project_settings_response_200.py +128 -0
  400. sendandretain/models/update_project_settings_response_200_utm_type_0.py +47 -0
  401. sendandretain/models/update_segment_body.py +109 -0
  402. sendandretain/models/update_segment_body_filter_item.py +81 -0
  403. sendandretain/models/update_segment_body_filter_item_op.py +17 -0
  404. sendandretain/models/update_segment_response_200.py +61 -0
  405. sendandretain/models/update_sender_body.py +90 -0
  406. sendandretain/models/update_sender_response_200.py +70 -0
  407. sendandretain/models/update_step_schema_0.py +108 -0
  408. sendandretain/models/update_step_schema_0_conditions_variant_0.py +91 -0
  409. sendandretain/models/update_step_schema_0_conditions_variant_0_op.py +17 -0
  410. sendandretain/models/update_step_schema_0_op.py +9 -0
  411. sendandretain/models/update_template_body.py +132 -0
  412. sendandretain/models/update_template_body_category.py +9 -0
  413. sendandretain/models/update_template_body_variables_item.py +107 -0
  414. sendandretain/models/update_template_body_variables_item_type.py +12 -0
  415. sendandretain/models/update_template_meta_body.py +102 -0
  416. sendandretain/models/update_template_meta_response_200.py +81 -0
  417. sendandretain/models/update_template_response_200.py +87 -0
  418. sendandretain/models/update_template_response_200_status.py +8 -0
  419. sendandretain/models/update_webhook_endpoint_body.py +102 -0
  420. sendandretain/models/update_webhook_endpoint_response_200.py +294 -0
  421. sendandretain/models/upsert_contact_body.py +107 -0
  422. sendandretain/models/upsert_contact_body_attributes.py +47 -0
  423. sendandretain/models/verify_domain_response_200.py +161 -0
  424. sendandretain/models/verify_domain_response_200_dmarc.py +110 -0
  425. sendandretain/models/verify_domain_response_200_dmarc_recommended_record.py +89 -0
  426. sendandretain/models/verify_domain_response_200_dns_records_item.py +47 -0
  427. sendandretain/models/verify_domain_response_200_tracking_type_0.py +115 -0
  428. sendandretain/models/webhook_event_email_bounced.py +112 -0
  429. sendandretain/models/webhook_event_email_bounced_data.py +112 -0
  430. sendandretain/models/webhook_event_email_bounced_data_detail.py +108 -0
  431. sendandretain/models/webhook_event_email_bounced_type.py +8 -0
  432. sendandretain/models/webhook_event_email_bounced_version.py +8 -0
  433. sendandretain/models/webhook_event_email_canceled.py +112 -0
  434. sendandretain/models/webhook_event_email_canceled_data.py +112 -0
  435. sendandretain/models/webhook_event_email_canceled_data_detail.py +47 -0
  436. sendandretain/models/webhook_event_email_canceled_type.py +8 -0
  437. sendandretain/models/webhook_event_email_canceled_version.py +8 -0
  438. sendandretain/models/webhook_event_email_clicked.py +112 -0
  439. sendandretain/models/webhook_event_email_clicked_data.py +112 -0
  440. sendandretain/models/webhook_event_email_clicked_data_detail.py +108 -0
  441. sendandretain/models/webhook_event_email_clicked_type.py +8 -0
  442. sendandretain/models/webhook_event_email_clicked_version.py +8 -0
  443. sendandretain/models/webhook_event_email_complained.py +112 -0
  444. sendandretain/models/webhook_event_email_complained_data.py +112 -0
  445. sendandretain/models/webhook_event_email_complained_data_detail.py +72 -0
  446. sendandretain/models/webhook_event_email_complained_type.py +8 -0
  447. sendandretain/models/webhook_event_email_complained_version.py +8 -0
  448. sendandretain/models/webhook_event_email_delivered.py +112 -0
  449. sendandretain/models/webhook_event_email_delivered_data.py +112 -0
  450. sendandretain/models/webhook_event_email_delivered_data_detail.py +47 -0
  451. sendandretain/models/webhook_event_email_delivered_type.py +8 -0
  452. sendandretain/models/webhook_event_email_delivered_version.py +8 -0
  453. sendandretain/models/webhook_event_email_delivery_delayed.py +112 -0
  454. sendandretain/models/webhook_event_email_delivery_delayed_data.py +112 -0
  455. sendandretain/models/webhook_event_email_delivery_delayed_data_detail.py +72 -0
  456. sendandretain/models/webhook_event_email_delivery_delayed_type.py +8 -0
  457. sendandretain/models/webhook_event_email_delivery_delayed_version.py +8 -0
  458. sendandretain/models/webhook_event_email_failed.py +112 -0
  459. sendandretain/models/webhook_event_email_failed_data.py +112 -0
  460. sendandretain/models/webhook_event_email_failed_data_detail.py +90 -0
  461. sendandretain/models/webhook_event_email_failed_type.py +8 -0
  462. sendandretain/models/webhook_event_email_failed_version.py +8 -0
  463. sendandretain/models/webhook_event_email_opened.py +112 -0
  464. sendandretain/models/webhook_event_email_opened_data.py +112 -0
  465. sendandretain/models/webhook_event_email_opened_data_detail.py +90 -0
  466. sendandretain/models/webhook_event_email_opened_type.py +8 -0
  467. sendandretain/models/webhook_event_email_opened_version.py +8 -0
  468. sendandretain/models/webhook_event_email_sent.py +112 -0
  469. sendandretain/models/webhook_event_email_sent_data.py +112 -0
  470. sendandretain/models/webhook_event_email_sent_data_detail.py +47 -0
  471. sendandretain/models/webhook_event_email_sent_type.py +8 -0
  472. sendandretain/models/webhook_event_email_sent_version.py +8 -0
  473. sendandretain/models/webhook_event_email_suppressed.py +112 -0
  474. sendandretain/models/webhook_event_email_suppressed_data.py +112 -0
  475. sendandretain/models/webhook_event_email_suppressed_data_detail.py +72 -0
  476. sendandretain/models/webhook_event_email_suppressed_type.py +8 -0
  477. sendandretain/models/webhook_event_email_suppressed_version.py +8 -0
  478. sendandretain/models/webhook_event_email_unsubscribed.py +112 -0
  479. sendandretain/models/webhook_event_email_unsubscribed_data.py +112 -0
  480. sendandretain/models/webhook_event_email_unsubscribed_data_detail.py +72 -0
  481. sendandretain/models/webhook_event_email_unsubscribed_type.py +8 -0
  482. sendandretain/models/webhook_event_email_unsubscribed_version.py +8 -0
  483. sendandretain/models/webhook_event_webhook_test.py +112 -0
  484. sendandretain/models/webhook_event_webhook_test_data.py +91 -0
  485. sendandretain/models/webhook_event_webhook_test_data_detail.py +47 -0
  486. sendandretain/models/webhook_event_webhook_test_type.py +8 -0
  487. sendandretain/models/webhook_event_webhook_test_version.py +8 -0
  488. sendandretain/models/webhook_message.py +220 -0
  489. sendandretain/models/webhook_message_tags_type_0_item.py +69 -0
  490. sendandretain/py.typed +0 -0
  491. sendandretain/types.py +54 -0
  492. sendandretain-0.2.0.dist-info/METADATA +267 -0
  493. sendandretain-0.2.0.dist-info/RECORD +495 -0
  494. sendandretain-0.2.0.dist-info/WHEEL +4 -0
  495. 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)
@@ -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)