dnscale 1.0.0__tar.gz

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 (301) hide show
  1. dnscale-1.0.0/GENERATION.md +24 -0
  2. dnscale-1.0.0/LICENSE +21 -0
  3. dnscale-1.0.0/MANIFEST.in +4 -0
  4. dnscale-1.0.0/PKG-INFO +200 -0
  5. dnscale-1.0.0/README.md +172 -0
  6. dnscale-1.0.0/dnscale/__init__.py +27 -0
  7. dnscale-1.0.0/dnscale/client.py +125 -0
  8. dnscale-1.0.0/dnscale/errors.py +118 -0
  9. dnscale-1.0.0/dnscale/pagination.py +20 -0
  10. dnscale-1.0.0/dnscale/py.typed +1 -0
  11. dnscale-1.0.0/dnscale/records.py +295 -0
  12. dnscale-1.0.0/dnscale/transport.py +129 -0
  13. dnscale-1.0.0/dnscale/zones.py +156 -0
  14. dnscale-1.0.0/dnscale.egg-info/PKG-INFO +200 -0
  15. dnscale-1.0.0/dnscale.egg-info/SOURCES.txt +299 -0
  16. dnscale-1.0.0/dnscale.egg-info/dependency_links.txt +1 -0
  17. dnscale-1.0.0/dnscale.egg-info/requires.txt +7 -0
  18. dnscale-1.0.0/dnscale.egg-info/top_level.txt +2 -0
  19. dnscale-1.0.0/dnscale_api/__init__.py +8 -0
  20. dnscale-1.0.0/dnscale_api/api/__init__.py +1 -0
  21. dnscale-1.0.0/dnscale_api/api/activity/__init__.py +1 -0
  22. dnscale-1.0.0/dnscale_api/api/activity/list_activity.py +255 -0
  23. dnscale-1.0.0/dnscale_api/api/alerts/__init__.py +1 -0
  24. dnscale-1.0.0/dnscale_api/api/alerts/create_alert.py +157 -0
  25. dnscale-1.0.0/dnscale_api/api/alerts/delete_alert.py +147 -0
  26. dnscale-1.0.0/dnscale_api/api/alerts/get_alert.py +151 -0
  27. dnscale-1.0.0/dnscale_api/api/alerts/list_alerts.py +208 -0
  28. dnscale-1.0.0/dnscale_api/api/alerts/update_alert.py +173 -0
  29. dnscale-1.0.0/dnscale_api/api/api_keys/__init__.py +1 -0
  30. dnscale-1.0.0/dnscale_api/api/api_keys/create_api_key.py +196 -0
  31. dnscale-1.0.0/dnscale_api/api/api_keys/delete_api_key.py +162 -0
  32. dnscale-1.0.0/dnscale_api/api/api_keys/get_api_key.py +168 -0
  33. dnscale-1.0.0/dnscale_api/api/api_keys/list_api_keys.py +154 -0
  34. dnscale-1.0.0/dnscale_api/api/billing/__init__.py +1 -0
  35. dnscale-1.0.0/dnscale_api/api/billing/get_billing_history.py +173 -0
  36. dnscale-1.0.0/dnscale_api/api/billing/get_billing_summary.py +158 -0
  37. dnscale-1.0.0/dnscale_api/api/billing/list_invoices.py +173 -0
  38. dnscale-1.0.0/dnscale_api/api/dns_control/__init__.py +1 -0
  39. dnscale-1.0.0/dnscale_api/api/dns_control/create_dns_control_connection.py +159 -0
  40. dnscale-1.0.0/dnscale_api/api/dns_control/create_dns_control_connection_zone_snapshot.py +191 -0
  41. dnscale-1.0.0/dnscale_api/api/dns_control/create_dns_control_inventory_snapshot.py +154 -0
  42. dnscale-1.0.0/dnscale_api/api/dns_control/diff_dns_control_snapshots.py +182 -0
  43. dnscale-1.0.0/dnscale_api/api/dns_control/get_dns_control_snapshot.py +168 -0
  44. dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_connection_zones.py +154 -0
  45. dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_connections.py +126 -0
  46. dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_inventory.py +126 -0
  47. dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_providers.py +126 -0
  48. dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_snapshots.py +190 -0
  49. dnscale-1.0.0/dnscale_api/api/dns_control/save_dns_control_zone_inventory.py +191 -0
  50. dnscale-1.0.0/dnscale_api/api/dnssec/__init__.py +1 -0
  51. dnscale-1.0.0/dnscale_api/api/dnssec/create_dnssec_cryptokey.py +176 -0
  52. dnscale-1.0.0/dnscale_api/api/dnssec/delete_dnssec_cryptokey.py +162 -0
  53. dnscale-1.0.0/dnscale_api/api/dnssec/get_dnssec_cryptokey.py +168 -0
  54. dnscale-1.0.0/dnscale_api/api/dnssec/get_dnssec_status.py +154 -0
  55. dnscale-1.0.0/dnscale_api/api/dnssec/list_dnssec_cryptokeys.py +154 -0
  56. dnscale-1.0.0/dnscale_api/api/dnssec/list_dnssecds_records.py +154 -0
  57. dnscale-1.0.0/dnscale_api/api/dnssec/set_dnssec_status.py +170 -0
  58. dnscale-1.0.0/dnscale_api/api/dnssec/update_dnssec_cryptokey.py +184 -0
  59. dnscale-1.0.0/dnscale_api/api/health/__init__.py +1 -0
  60. dnscale-1.0.0/dnscale_api/api/health/get_health.py +123 -0
  61. dnscale-1.0.0/dnscale_api/api/invitations/__init__.py +1 -0
  62. dnscale-1.0.0/dnscale_api/api/invitations/create_invitation.py +159 -0
  63. dnscale-1.0.0/dnscale_api/api/invitations/delete_invitation.py +148 -0
  64. dnscale-1.0.0/dnscale_api/api/invitations/get_invitation.py +154 -0
  65. dnscale-1.0.0/dnscale_api/api/invitations/list_invitations.py +173 -0
  66. dnscale-1.0.0/dnscale_api/api/records/__init__.py +1 -0
  67. dnscale-1.0.0/dnscale_api/api/records/create_record.py +176 -0
  68. dnscale-1.0.0/dnscale_api/api/records/delete_record.py +162 -0
  69. dnscale-1.0.0/dnscale_api/api/records/delete_records_by_name_and_type.py +206 -0
  70. dnscale-1.0.0/dnscale_api/api/records/get_record.py +168 -0
  71. dnscale-1.0.0/dnscale_api/api/records/list_records.py +190 -0
  72. dnscale-1.0.0/dnscale_api/api/records/update_record.py +198 -0
  73. dnscale-1.0.0/dnscale_api/api/records/update_record_by_name_and_type.py +233 -0
  74. dnscale-1.0.0/dnscale_api/api/usage/__init__.py +1 -0
  75. dnscale-1.0.0/dnscale_api/api/usage/get_current_usage.py +126 -0
  76. dnscale-1.0.0/dnscale_api/api/usage/get_daily_usage.py +199 -0
  77. dnscale-1.0.0/dnscale_api/api/usage/get_usage_summary.py +158 -0
  78. dnscale-1.0.0/dnscale_api/api/usage/get_zone_usage.py +197 -0
  79. dnscale-1.0.0/dnscale_api/api/usage/list_top_zones_by_usage.py +195 -0
  80. dnscale-1.0.0/dnscale_api/api/users/__init__.py +1 -0
  81. dnscale-1.0.0/dnscale_api/api/users/create_user.py +157 -0
  82. dnscale-1.0.0/dnscale_api/api/users/delete_user.py +148 -0
  83. dnscale-1.0.0/dnscale_api/api/users/get_user.py +152 -0
  84. dnscale-1.0.0/dnscale_api/api/users/list_users.py +171 -0
  85. dnscale-1.0.0/dnscale_api/api/users/update_user.py +178 -0
  86. dnscale-1.0.0/dnscale_api/api/zones/__init__.py +1 -0
  87. dnscale-1.0.0/dnscale_api/api/zones/create_zone.py +157 -0
  88. dnscale-1.0.0/dnscale_api/api/zones/delete_zone.py +148 -0
  89. dnscale-1.0.0/dnscale_api/api/zones/get_zone.py +152 -0
  90. dnscale-1.0.0/dnscale_api/api/zones/list_zones.py +171 -0
  91. dnscale-1.0.0/dnscale_api/api/zones/update_zone.py +178 -0
  92. dnscale-1.0.0/dnscale_api/client.py +268 -0
  93. dnscale-1.0.0/dnscale_api/errors.py +16 -0
  94. dnscale-1.0.0/dnscale_api/models/__init__.py +399 -0
  95. dnscale-1.0.0/dnscale_api/models/activity_data.py +66 -0
  96. dnscale-1.0.0/dnscale_api/models/activity_item.py +236 -0
  97. dnscale-1.0.0/dnscale_api/models/activity_item_new_values_type_0.py +46 -0
  98. dnscale-1.0.0/dnscale_api/models/activity_item_old_values_type_0.py +46 -0
  99. dnscale-1.0.0/dnscale_api/models/activity_response.py +58 -0
  100. dnscale-1.0.0/dnscale_api/models/activity_response_status.py +8 -0
  101. dnscale-1.0.0/dnscale_api/models/aggregated_daily_stat.py +92 -0
  102. dnscale-1.0.0/dnscale_api/models/aggregated_usage_response.py +100 -0
  103. dnscale-1.0.0/dnscale_api/models/aggregated_usage_response_envelope.py +58 -0
  104. dnscale-1.0.0/dnscale_api/models/aggregated_usage_response_envelope_status.py +8 -0
  105. dnscale-1.0.0/dnscale_api/models/alert.py +210 -0
  106. dnscale-1.0.0/dnscale_api/models/alert_actions.py +46 -0
  107. dnscale-1.0.0/dnscale_api/models/alert_channel.py +125 -0
  108. dnscale-1.0.0/dnscale_api/models/alert_channel_type.py +11 -0
  109. dnscale-1.0.0/dnscale_api/models/alert_data.py +48 -0
  110. dnscale-1.0.0/dnscale_api/models/alert_response.py +58 -0
  111. dnscale-1.0.0/dnscale_api/models/alert_response_status.py +8 -0
  112. dnscale-1.0.0/dnscale_api/models/alert_scope_type.py +9 -0
  113. dnscale-1.0.0/dnscale_api/models/alert_severity.py +10 -0
  114. dnscale-1.0.0/dnscale_api/models/alert_status.py +9 -0
  115. dnscale-1.0.0/dnscale_api/models/alert_threshold_config.py +114 -0
  116. dnscale-1.0.0/dnscale_api/models/alerts_data.py +56 -0
  117. dnscale-1.0.0/dnscale_api/models/alerts_response.py +58 -0
  118. dnscale-1.0.0/dnscale_api/models/alerts_response_status.py +8 -0
  119. dnscale-1.0.0/dnscale_api/models/api_key.py +172 -0
  120. dnscale-1.0.0/dnscale_api/models/api_key_data.py +48 -0
  121. dnscale-1.0.0/dnscale_api/models/api_key_response.py +58 -0
  122. dnscale-1.0.0/dnscale_api/models/api_key_response_status.py +8 -0
  123. dnscale-1.0.0/dnscale_api/models/api_key_zone_scope.py +66 -0
  124. dnscale-1.0.0/dnscale_api/models/api_key_zone_scope_record_access.py +9 -0
  125. dnscale-1.0.0/dnscale_api/models/api_keys_data.py +56 -0
  126. dnscale-1.0.0/dnscale_api/models/api_keys_response.py +58 -0
  127. dnscale-1.0.0/dnscale_api/models/api_keys_response_status.py +8 -0
  128. dnscale-1.0.0/dnscale_api/models/billing_history.py +64 -0
  129. dnscale-1.0.0/dnscale_api/models/billing_history_response_envelope.py +58 -0
  130. dnscale-1.0.0/dnscale_api/models/billing_history_response_envelope_status.py +8 -0
  131. dnscale-1.0.0/dnscale_api/models/billing_summary.py +127 -0
  132. dnscale-1.0.0/dnscale_api/models/billing_summary_response_envelope.py +58 -0
  133. dnscale-1.0.0/dnscale_api/models/billing_summary_response_envelope_status.py +8 -0
  134. dnscale-1.0.0/dnscale_api/models/create_alert_request.py +162 -0
  135. dnscale-1.0.0/dnscale_api/models/create_alert_request_actions.py +46 -0
  136. dnscale-1.0.0/dnscale_api/models/create_api_key_request.py +140 -0
  137. dnscale-1.0.0/dnscale_api/models/create_cryptokey_request.py +135 -0
  138. dnscale-1.0.0/dnscale_api/models/create_cryptokey_request_keytype.py +10 -0
  139. dnscale-1.0.0/dnscale_api/models/create_dns_control_connection_request.py +144 -0
  140. dnscale-1.0.0/dnscale_api/models/create_dns_control_connection_request_mode.py +8 -0
  141. dnscale-1.0.0/dnscale_api/models/create_dns_control_connection_request_provider.py +9 -0
  142. dnscale-1.0.0/dnscale_api/models/create_invitation_request.py +42 -0
  143. dnscale-1.0.0/dnscale_api/models/create_record_request.py +119 -0
  144. dnscale-1.0.0/dnscale_api/models/create_user_request.py +79 -0
  145. dnscale-1.0.0/dnscale_api/models/create_zone_request.py +95 -0
  146. dnscale-1.0.0/dnscale_api/models/cryptokey.py +127 -0
  147. dnscale-1.0.0/dnscale_api/models/cryptokey_data.py +48 -0
  148. dnscale-1.0.0/dnscale_api/models/cryptokey_keytype.py +10 -0
  149. dnscale-1.0.0/dnscale_api/models/cryptokey_response.py +58 -0
  150. dnscale-1.0.0/dnscale_api/models/cryptokey_response_status.py +8 -0
  151. dnscale-1.0.0/dnscale_api/models/cryptokeys_data.py +56 -0
  152. dnscale-1.0.0/dnscale_api/models/cryptokeys_response.py +58 -0
  153. dnscale-1.0.0/dnscale_api/models/cryptokeys_response_status.py +8 -0
  154. dnscale-1.0.0/dnscale_api/models/customer_usage_response.py +58 -0
  155. dnscale-1.0.0/dnscale_api/models/customer_usage_response_status.py +8 -0
  156. dnscale-1.0.0/dnscale_api/models/customer_usage_summary.py +191 -0
  157. dnscale-1.0.0/dnscale_api/models/customer_usage_summary_billing_type.py +9 -0
  158. dnscale-1.0.0/dnscale_api/models/dns_control_auth_field.py +87 -0
  159. dnscale-1.0.0/dnscale_api/models/dns_control_auth_field_type.py +10 -0
  160. dnscale-1.0.0/dnscale_api/models/dns_control_connection.py +209 -0
  161. dnscale-1.0.0/dnscale_api/models/dns_control_connection_data.py +48 -0
  162. dnscale-1.0.0/dnscale_api/models/dns_control_connection_mode.py +8 -0
  163. dnscale-1.0.0/dnscale_api/models/dns_control_connection_response.py +58 -0
  164. dnscale-1.0.0/dnscale_api/models/dns_control_connection_response_status.py +8 -0
  165. dnscale-1.0.0/dnscale_api/models/dns_control_connection_snapshot_data.py +58 -0
  166. dnscale-1.0.0/dnscale_api/models/dns_control_connection_snapshot_response.py +60 -0
  167. dnscale-1.0.0/dnscale_api/models/dns_control_connection_snapshot_response_status.py +8 -0
  168. dnscale-1.0.0/dnscale_api/models/dns_control_connection_status.py +11 -0
  169. dnscale-1.0.0/dnscale_api/models/dns_control_connections_data.py +56 -0
  170. dnscale-1.0.0/dnscale_api/models/dns_control_connections_response.py +58 -0
  171. dnscale-1.0.0/dnscale_api/models/dns_control_connections_response_status.py +8 -0
  172. dnscale-1.0.0/dnscale_api/models/dns_control_inventory.py +149 -0
  173. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_data.py +48 -0
  174. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_delegation.py +46 -0
  175. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_dnssec.py +46 -0
  176. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_list_data.py +56 -0
  177. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_list_response.py +60 -0
  178. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_list_response_status.py +8 -0
  179. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_response.py +58 -0
  180. dnscale-1.0.0/dnscale_api/models/dns_control_inventory_response_status.py +8 -0
  181. dnscale-1.0.0/dnscale_api/models/dns_control_provider.py +121 -0
  182. dnscale-1.0.0/dnscale_api/models/dns_control_provider_auth_type.py +11 -0
  183. dnscale-1.0.0/dnscale_api/models/dns_control_provider_capabilities.py +135 -0
  184. dnscale-1.0.0/dnscale_api/models/dns_control_provider_type.py +9 -0
  185. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone.py +121 -0
  186. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone_dnssec.py +46 -0
  187. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone_metadata.py +46 -0
  188. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone_request.py +42 -0
  189. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zones_data.py +56 -0
  190. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zones_response.py +60 -0
  191. dnscale-1.0.0/dnscale_api/models/dns_control_provider_zones_response_status.py +8 -0
  192. dnscale-1.0.0/dnscale_api/models/dns_control_providers_data.py +56 -0
  193. dnscale-1.0.0/dnscale_api/models/dns_control_providers_response.py +58 -0
  194. dnscale-1.0.0/dnscale_api/models/dns_control_providers_response_status.py +8 -0
  195. dnscale-1.0.0/dnscale_api/models/dns_control_record_set.py +191 -0
  196. dnscale-1.0.0/dnscale_api/models/dns_control_record_set_provider_metadata.py +46 -0
  197. dnscale-1.0.0/dnscale_api/models/dns_control_record_set_raw_refs_item.py +46 -0
  198. dnscale-1.0.0/dnscale_api/models/dns_control_record_set_values_item.py +46 -0
  199. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot.py +141 -0
  200. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_canonical_zone.py +46 -0
  201. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_data.py +48 -0
  202. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff.py +83 -0
  203. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change.py +133 -0
  204. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_after.py +46 -0
  205. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_before.py +46 -0
  206. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_severity.py +10 -0
  207. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_type.py +12 -0
  208. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_data.py +48 -0
  209. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_response.py +58 -0
  210. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_response_status.py +8 -0
  211. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_summary.py +82 -0
  212. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_response.py +58 -0
  213. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_response_status.py +8 -0
  214. dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_source_kind.py +8 -0
  215. dnscale-1.0.0/dnscale_api/models/dns_control_snapshots_data.py +66 -0
  216. dnscale-1.0.0/dnscale_api/models/dns_control_snapshots_response.py +58 -0
  217. dnscale-1.0.0/dnscale_api/models/dns_control_snapshots_response_status.py +8 -0
  218. dnscale-1.0.0/dnscale_api/models/dnssec_status_data.py +42 -0
  219. dnscale-1.0.0/dnscale_api/models/dnssec_status_response.py +58 -0
  220. dnscale-1.0.0/dnscale_api/models/dnssec_status_response_status.py +8 -0
  221. dnscale-1.0.0/dnscale_api/models/ds_records_data.py +42 -0
  222. dnscale-1.0.0/dnscale_api/models/ds_records_response.py +58 -0
  223. dnscale-1.0.0/dnscale_api/models/ds_records_response_status.py +8 -0
  224. dnscale-1.0.0/dnscale_api/models/error_detail.py +79 -0
  225. dnscale-1.0.0/dnscale_api/models/error_detail_details_item.py +46 -0
  226. dnscale-1.0.0/dnscale_api/models/error_response.py +58 -0
  227. dnscale-1.0.0/dnscale_api/models/error_response_status.py +8 -0
  228. dnscale-1.0.0/dnscale_api/models/health.py +62 -0
  229. dnscale-1.0.0/dnscale_api/models/health_data.py +48 -0
  230. dnscale-1.0.0/dnscale_api/models/health_response.py +58 -0
  231. dnscale-1.0.0/dnscale_api/models/health_response_status.py +8 -0
  232. dnscale-1.0.0/dnscale_api/models/health_status.py +8 -0
  233. dnscale-1.0.0/dnscale_api/models/invitation.py +113 -0
  234. dnscale-1.0.0/dnscale_api/models/invitation_data.py +48 -0
  235. dnscale-1.0.0/dnscale_api/models/invitation_response.py +58 -0
  236. dnscale-1.0.0/dnscale_api/models/invitation_response_status.py +8 -0
  237. dnscale-1.0.0/dnscale_api/models/invitation_status.py +11 -0
  238. dnscale-1.0.0/dnscale_api/models/invitations_data.py +56 -0
  239. dnscale-1.0.0/dnscale_api/models/invitations_response.py +58 -0
  240. dnscale-1.0.0/dnscale_api/models/invitations_response_status.py +8 -0
  241. dnscale-1.0.0/dnscale_api/models/invoice.py +281 -0
  242. dnscale-1.0.0/dnscale_api/models/invoices_data.py +64 -0
  243. dnscale-1.0.0/dnscale_api/models/invoices_response.py +58 -0
  244. dnscale-1.0.0/dnscale_api/models/invoices_response_status.py +8 -0
  245. dnscale-1.0.0/dnscale_api/models/offset_meta.py +50 -0
  246. dnscale-1.0.0/dnscale_api/models/pagination.py +74 -0
  247. dnscale-1.0.0/dnscale_api/models/provider_credentials.py +79 -0
  248. dnscale-1.0.0/dnscale_api/models/record.py +125 -0
  249. dnscale-1.0.0/dnscale_api/models/record_data.py +48 -0
  250. dnscale-1.0.0/dnscale_api/models/record_response.py +58 -0
  251. dnscale-1.0.0/dnscale_api/models/record_response_status.py +8 -0
  252. dnscale-1.0.0/dnscale_api/models/record_type.py +22 -0
  253. dnscale-1.0.0/dnscale_api/models/records_data.py +66 -0
  254. dnscale-1.0.0/dnscale_api/models/records_response.py +58 -0
  255. dnscale-1.0.0/dnscale_api/models/records_response_status.py +8 -0
  256. dnscale-1.0.0/dnscale_api/models/set_dnssec_request.py +42 -0
  257. dnscale-1.0.0/dnscale_api/models/top_zones_data.py +56 -0
  258. dnscale-1.0.0/dnscale_api/models/top_zones_response.py +58 -0
  259. dnscale-1.0.0/dnscale_api/models/top_zones_response_status.py +8 -0
  260. dnscale-1.0.0/dnscale_api/models/update_alert_request.py +176 -0
  261. dnscale-1.0.0/dnscale_api/models/update_alert_request_actions.py +46 -0
  262. dnscale-1.0.0/dnscale_api/models/update_cryptokey_request.py +51 -0
  263. dnscale-1.0.0/dnscale_api/models/update_record_by_name_request.py +102 -0
  264. dnscale-1.0.0/dnscale_api/models/update_user_request.py +80 -0
  265. dnscale-1.0.0/dnscale_api/models/update_zone_request.py +96 -0
  266. dnscale-1.0.0/dnscale_api/models/usage_map.py +46 -0
  267. dnscale-1.0.0/dnscale_api/models/user.py +124 -0
  268. dnscale-1.0.0/dnscale_api/models/user_data.py +48 -0
  269. dnscale-1.0.0/dnscale_api/models/user_response.py +58 -0
  270. dnscale-1.0.0/dnscale_api/models/user_response_status.py +8 -0
  271. dnscale-1.0.0/dnscale_api/models/user_role.py +9 -0
  272. dnscale-1.0.0/dnscale_api/models/users_data.py +56 -0
  273. dnscale-1.0.0/dnscale_api/models/users_response.py +58 -0
  274. dnscale-1.0.0/dnscale_api/models/users_response_status.py +8 -0
  275. dnscale-1.0.0/dnscale_api/models/zone.py +128 -0
  276. dnscale-1.0.0/dnscale_api/models/zone_data.py +48 -0
  277. dnscale-1.0.0/dnscale_api/models/zone_region.py +10 -0
  278. dnscale-1.0.0/dnscale_api/models/zone_response.py +58 -0
  279. dnscale-1.0.0/dnscale_api/models/zone_response_status.py +8 -0
  280. dnscale-1.0.0/dnscale_api/models/zone_settings_brief.py +42 -0
  281. dnscale-1.0.0/dnscale_api/models/zone_status.py +11 -0
  282. dnscale-1.0.0/dnscale_api/models/zone_type.py +9 -0
  283. dnscale-1.0.0/dnscale_api/models/zone_usage_daily.py +163 -0
  284. dnscale-1.0.0/dnscale_api/models/zone_usage_response.py +58 -0
  285. dnscale-1.0.0/dnscale_api/models/zone_usage_response_status.py +8 -0
  286. dnscale-1.0.0/dnscale_api/models/zone_usage_summary.py +128 -0
  287. dnscale-1.0.0/dnscale_api/models/zones_data.py +66 -0
  288. dnscale-1.0.0/dnscale_api/models/zones_response.py +102 -0
  289. dnscale-1.0.0/dnscale_api/models/zones_response_status.py +8 -0
  290. dnscale-1.0.0/dnscale_api/py.typed +1 -0
  291. dnscale-1.0.0/dnscale_api/types.py +54 -0
  292. dnscale-1.0.0/generator.yaml +6 -0
  293. dnscale-1.0.0/openapi.yaml +3418 -0
  294. dnscale-1.0.0/pyproject.toml +60 -0
  295. dnscale-1.0.0/scripts/generate.sh +8 -0
  296. dnscale-1.0.0/setup.cfg +4 -0
  297. dnscale-1.0.0/tests/test_api_key_scope.py +31 -0
  298. dnscale-1.0.0/tests/test_client.py +175 -0
  299. dnscale-1.0.0/tests/test_distribution.py +22 -0
  300. dnscale-1.0.0/tests/test_live.py +28 -0
  301. dnscale-1.0.0/tests/test_resources.py +219 -0
@@ -0,0 +1,24 @@
1
+ # Generated client policy
2
+
3
+ `dnscale_api/` is generated from the bundled `openapi.yaml`; do not edit it manually.
4
+ `dnscale/` is the stable, handwritten layer and is preserved during regeneration.
5
+
6
+ From the repository root:
7
+
8
+ ```bash
9
+ bash scripts/generate.sh
10
+ ```
11
+
12
+ The script pins `openapi-python-client` so regeneration is deterministic. Review
13
+ the generated diff whenever the OpenAPI contract changes, then run the SDK tests
14
+ and build before committing both changes together.
15
+
16
+ Generator formatting ignores Git ignore rules within the generated output so
17
+ the same model files are formatted in standalone and monorepo checkouts. The
18
+ generated `provider_credentials.py` contains schema definitions, not secrets,
19
+ and must remain tracked despite the monorepo's broad credentials ignore rule.
20
+
21
+ Its inputs are the bundled `openapi.yaml` and `generator.yaml`. These are
22
+ maintainer-synchronized copies of the authoritative API contract and generator
23
+ configuration. Do not edit the copies to change API behaviour; propose API
24
+ changes to the maintainers first, then regenerate from the approved contract.
dnscale-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DNScale
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include GENERATION.md openapi.yaml generator.yaml
2
+ recursive-include scripts *.sh
3
+ exclude RELEASING.md .pypirc .npmrc
4
+ global-exclude .env .env.* *.pem *.key
dnscale-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.4
2
+ Name: dnscale
3
+ Version: 1.0.0
4
+ Summary: Official Python SDK for the DNScale API
5
+ Author-email: DNScale <ops@dnscale.eu>
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://docs.dnscale.eu
8
+ Project-URL: Homepage, https://dnscale.eu
9
+ Project-URL: Repository, https://github.com/dnscaleou/dnscale-python
10
+ Project-URL: Issues, https://github.com/dnscaleou/dnscale-python/issues
11
+ Keywords: dns,dnscale,api,sdk
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: attrs>=22.2.0
22
+ Requires-Dist: httpx<0.29.0,>=0.23.0
23
+ Requires-Dist: python-dateutil<3,>=2.8.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest<9,>=8.3.0; extra == "dev"
26
+ Requires-Dist: ruff<0.13,>=0.12.0; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # DNScale Python SDK
30
+
31
+ The official Python client for the DNScale API. It supports Python 3.10 and newer,
32
+ provides synchronous and asynchronous calls, and is generated from the public
33
+ OpenAPI contract bundled as [openapi.yaml](openapi.yaml).
34
+
35
+ ## Installation
36
+
37
+ Install a published release from PyPI:
38
+
39
+ ```bash
40
+ python -m pip install dnscale
41
+ ```
42
+
43
+ The distribution and import name are both `dnscale`; the source repository is
44
+ `dnscale-python`. Before the first PyPI release, or to work from source, clone
45
+ this repository and install from its root:
46
+
47
+ ```bash
48
+ python -m pip install .
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ Create an API key in the DNScale dashboard and pass it directly or export it as
54
+ `DNSCALE_API_KEY`.
55
+
56
+ ```python
57
+ from dnscale import DNScale
58
+
59
+ with DNScale() as dns:
60
+ zones = dns.zones.list(limit=25)
61
+ for zone in zones.zones:
62
+ print(zone.id, zone.name)
63
+
64
+ created = dns.records.create(
65
+ zones.zones[0].id,
66
+ name="www",
67
+ type_="A",
68
+ content="192.0.2.10",
69
+ ttl=300,
70
+ )
71
+ print(created.id)
72
+ ```
73
+
74
+ The same resources provide async methods prefixed with `a`:
75
+
76
+ ```python
77
+ from dnscale import DNScale
78
+
79
+
80
+ async def list_zone_names() -> list[str]:
81
+ async with DNScale() as dns:
82
+ result = await dns.zones.alist(limit=100)
83
+ return [zone.name for zone in result.zones]
84
+ ```
85
+
86
+ `DNScale` retries transient failures for `GET`, `HEAD`, and `OPTIONS` requests.
87
+ It never automatically retries mutations such as record or zone creation. Set
88
+ `max_retries=0` to disable retries.
89
+ If `Retry-After` exceeds the 30-second retry waiting budget, the response is
90
+ surfaced immediately instead of retrying before the server permits it. HTTP-date
91
+ and numeric delay values are supported. Network errors remain `httpx` errors.
92
+
93
+ ## Pagination and record identity
94
+
95
+ `list()` returns one page. Use lazy iterators to traverse all pages:
96
+
97
+ ```python
98
+ with DNScale() as dns:
99
+ for zone in dns.zones.iter():
100
+ for record in dns.records.iter(zone.id):
101
+ print(zone.name, record.name, record.content)
102
+ ```
103
+
104
+ Async equivalents are `zones.aiter()` and `records.aiter(zone_id)`. Iterators
105
+ honour the returned page size and reject inconsistent pagination. Offset-based
106
+ listing is not an atomic snapshot if another client changes the zone concurrently.
107
+
108
+ Record IDs are opaque and content-derived: use the **returned ID after an
109
+ update**. By-name helpers can avoid retaining an ID:
110
+
111
+ ```python
112
+ from dnscale_api.models import UpdateRecordByNameRequest
113
+
114
+ with DNScale() as dns:
115
+ updated = dns.records.update_by_name(
116
+ zone_id, "_acme-challenge.example.com.", "TXT",
117
+ content="old-value", # select this value in a multi-value RRset
118
+ body=UpdateRecordByNameRequest(content="new-value", ttl=300),
119
+ )
120
+ dns.records.delete_by_name(zone_id, updated.name, "TXT", content="new-value")
121
+ ```
122
+
123
+ Omitting `content` from `delete_by_name()` deletes the **entire RRset**. Empty
124
+ content is rejected by the helper to prevent accidental whole-RRset deletion.
125
+ Async variants are `aupdate_by_name()` and `adelete_by_name()`.
126
+
127
+ ## Errors
128
+
129
+ The convenience resources convert the API's error envelope into exceptions:
130
+
131
+ ```python
132
+ from dnscale import APIError, DNScale, RateLimitError
133
+
134
+ try:
135
+ with DNScale() as dns:
136
+ dns.zones.get("00000000-0000-0000-0000-000000000000")
137
+ except RateLimitError as error:
138
+ print(error.retry_after)
139
+ except APIError as error:
140
+ print(error.status_code, error.code, error.message, error.request_id)
141
+ ```
142
+
143
+ Network and timeout failures remain standard `httpx` exceptions.
144
+
145
+ ## Complete generated API
146
+
147
+ The `dnscale` layer offers concise zone and record CRUD. Every public endpoint is
148
+ also available through the generated `dnscale_api` package, including alerts,
149
+ DNSSEC, usage, billing, users, invitations, API keys, activity, and DNS Control.
150
+
151
+ ```python
152
+ from dnscale import DNScale
153
+ from dnscale_api.api.usage import get_current_usage
154
+
155
+ with DNScale(api_key="your-api-key") as dns:
156
+ response = get_current_usage.sync(client=dns.client)
157
+ print(response)
158
+ ```
159
+
160
+ Each generated operation has `sync`, `sync_detailed`, `asyncio`, and
161
+ `asyncio_detailed` variants. Request and response models live in
162
+ `dnscale_api.models`.
163
+
164
+ ## Development
165
+
166
+ From this repository's root, install development dependencies and regenerate
167
+ the client. Python 3.10+ and [uv](https://docs.astral.sh/uv/) are required.
168
+ Do not edit `dnscale_api` by hand.
169
+
170
+ ```bash
171
+ python -m pip install -e '.[dev]'
172
+ bash scripts/generate.sh
173
+ ```
174
+
175
+ The generator version is pinned in the script. To verify the package:
176
+
177
+ ```bash
178
+ python -m pytest
179
+ ruff check dnscale tests
180
+ ruff format --check dnscale tests
181
+ uv build
182
+ ```
183
+
184
+ The generator is pinned; see [GENERATION.md](GENERATION.md) for the contract
185
+ update policy. Commit the reviewed generated changes with their input changes.
186
+
187
+ Live verification is explicitly opt-in and creates/deletes a unique test zone:
188
+
189
+ ```bash
190
+ export DNSCALE_TEST_BASE_URL=https://your-sandbox.example/v1
191
+ export DNSCALE_TEST_API_KEY=your-disposable-account-key
192
+ DNSCALE_LIVE_TEST=1 python -m pytest tests/test_live.py
193
+ ```
194
+
195
+ The test uses separate credentials from `DNSCALE_API_KEY`. Use a disposable
196
+ account with zone and record read/write scopes. No real domain delegation is
197
+ needed. Tests are skipped when `DNSCALE_LIVE_TEST` is not `1`.
198
+
199
+ Report bugs and feature requests in the
200
+ [issue tracker](https://github.com/dnscaleou/dnscale-python/issues).
@@ -0,0 +1,172 @@
1
+ # DNScale Python SDK
2
+
3
+ The official Python client for the DNScale API. It supports Python 3.10 and newer,
4
+ provides synchronous and asynchronous calls, and is generated from the public
5
+ OpenAPI contract bundled as [openapi.yaml](openapi.yaml).
6
+
7
+ ## Installation
8
+
9
+ Install a published release from PyPI:
10
+
11
+ ```bash
12
+ python -m pip install dnscale
13
+ ```
14
+
15
+ The distribution and import name are both `dnscale`; the source repository is
16
+ `dnscale-python`. Before the first PyPI release, or to work from source, clone
17
+ this repository and install from its root:
18
+
19
+ ```bash
20
+ python -m pip install .
21
+ ```
22
+
23
+ ## Quick start
24
+
25
+ Create an API key in the DNScale dashboard and pass it directly or export it as
26
+ `DNSCALE_API_KEY`.
27
+
28
+ ```python
29
+ from dnscale import DNScale
30
+
31
+ with DNScale() as dns:
32
+ zones = dns.zones.list(limit=25)
33
+ for zone in zones.zones:
34
+ print(zone.id, zone.name)
35
+
36
+ created = dns.records.create(
37
+ zones.zones[0].id,
38
+ name="www",
39
+ type_="A",
40
+ content="192.0.2.10",
41
+ ttl=300,
42
+ )
43
+ print(created.id)
44
+ ```
45
+
46
+ The same resources provide async methods prefixed with `a`:
47
+
48
+ ```python
49
+ from dnscale import DNScale
50
+
51
+
52
+ async def list_zone_names() -> list[str]:
53
+ async with DNScale() as dns:
54
+ result = await dns.zones.alist(limit=100)
55
+ return [zone.name for zone in result.zones]
56
+ ```
57
+
58
+ `DNScale` retries transient failures for `GET`, `HEAD`, and `OPTIONS` requests.
59
+ It never automatically retries mutations such as record or zone creation. Set
60
+ `max_retries=0` to disable retries.
61
+ If `Retry-After` exceeds the 30-second retry waiting budget, the response is
62
+ surfaced immediately instead of retrying before the server permits it. HTTP-date
63
+ and numeric delay values are supported. Network errors remain `httpx` errors.
64
+
65
+ ## Pagination and record identity
66
+
67
+ `list()` returns one page. Use lazy iterators to traverse all pages:
68
+
69
+ ```python
70
+ with DNScale() as dns:
71
+ for zone in dns.zones.iter():
72
+ for record in dns.records.iter(zone.id):
73
+ print(zone.name, record.name, record.content)
74
+ ```
75
+
76
+ Async equivalents are `zones.aiter()` and `records.aiter(zone_id)`. Iterators
77
+ honour the returned page size and reject inconsistent pagination. Offset-based
78
+ listing is not an atomic snapshot if another client changes the zone concurrently.
79
+
80
+ Record IDs are opaque and content-derived: use the **returned ID after an
81
+ update**. By-name helpers can avoid retaining an ID:
82
+
83
+ ```python
84
+ from dnscale_api.models import UpdateRecordByNameRequest
85
+
86
+ with DNScale() as dns:
87
+ updated = dns.records.update_by_name(
88
+ zone_id, "_acme-challenge.example.com.", "TXT",
89
+ content="old-value", # select this value in a multi-value RRset
90
+ body=UpdateRecordByNameRequest(content="new-value", ttl=300),
91
+ )
92
+ dns.records.delete_by_name(zone_id, updated.name, "TXT", content="new-value")
93
+ ```
94
+
95
+ Omitting `content` from `delete_by_name()` deletes the **entire RRset**. Empty
96
+ content is rejected by the helper to prevent accidental whole-RRset deletion.
97
+ Async variants are `aupdate_by_name()` and `adelete_by_name()`.
98
+
99
+ ## Errors
100
+
101
+ The convenience resources convert the API's error envelope into exceptions:
102
+
103
+ ```python
104
+ from dnscale import APIError, DNScale, RateLimitError
105
+
106
+ try:
107
+ with DNScale() as dns:
108
+ dns.zones.get("00000000-0000-0000-0000-000000000000")
109
+ except RateLimitError as error:
110
+ print(error.retry_after)
111
+ except APIError as error:
112
+ print(error.status_code, error.code, error.message, error.request_id)
113
+ ```
114
+
115
+ Network and timeout failures remain standard `httpx` exceptions.
116
+
117
+ ## Complete generated API
118
+
119
+ The `dnscale` layer offers concise zone and record CRUD. Every public endpoint is
120
+ also available through the generated `dnscale_api` package, including alerts,
121
+ DNSSEC, usage, billing, users, invitations, API keys, activity, and DNS Control.
122
+
123
+ ```python
124
+ from dnscale import DNScale
125
+ from dnscale_api.api.usage import get_current_usage
126
+
127
+ with DNScale(api_key="your-api-key") as dns:
128
+ response = get_current_usage.sync(client=dns.client)
129
+ print(response)
130
+ ```
131
+
132
+ Each generated operation has `sync`, `sync_detailed`, `asyncio`, and
133
+ `asyncio_detailed` variants. Request and response models live in
134
+ `dnscale_api.models`.
135
+
136
+ ## Development
137
+
138
+ From this repository's root, install development dependencies and regenerate
139
+ the client. Python 3.10+ and [uv](https://docs.astral.sh/uv/) are required.
140
+ Do not edit `dnscale_api` by hand.
141
+
142
+ ```bash
143
+ python -m pip install -e '.[dev]'
144
+ bash scripts/generate.sh
145
+ ```
146
+
147
+ The generator version is pinned in the script. To verify the package:
148
+
149
+ ```bash
150
+ python -m pytest
151
+ ruff check dnscale tests
152
+ ruff format --check dnscale tests
153
+ uv build
154
+ ```
155
+
156
+ The generator is pinned; see [GENERATION.md](GENERATION.md) for the contract
157
+ update policy. Commit the reviewed generated changes with their input changes.
158
+
159
+ Live verification is explicitly opt-in and creates/deletes a unique test zone:
160
+
161
+ ```bash
162
+ export DNSCALE_TEST_BASE_URL=https://your-sandbox.example/v1
163
+ export DNSCALE_TEST_API_KEY=your-disposable-account-key
164
+ DNSCALE_LIVE_TEST=1 python -m pytest tests/test_live.py
165
+ ```
166
+
167
+ The test uses separate credentials from `DNSCALE_API_KEY`. Use a disposable
168
+ account with zone and record read/write scopes. No real domain delegation is
169
+ needed. Tests are skipped when `DNSCALE_LIVE_TEST` is not `1`.
170
+
171
+ Report bugs and feature requests in the
172
+ [issue tracker](https://github.com/dnscaleou/dnscale-python/issues).
@@ -0,0 +1,27 @@
1
+ """Ergonomic entry point for the DNScale Python SDK."""
2
+
3
+ from dnscale_api.models.record import Record
4
+ from dnscale_api.models.record_type import RecordType
5
+ from dnscale_api.models.zone import Zone
6
+ from dnscale_api.models.zone_region import ZoneRegion
7
+ from dnscale_api.models.zone_status import ZoneStatus
8
+ from dnscale_api.models.zone_type import ZoneType
9
+
10
+ from .client import DEFAULT_BASE_URL, DNScale
11
+ from .errors import APIError, ProtocolError, RateLimitError
12
+
13
+ __all__ = (
14
+ "APIError",
15
+ "DEFAULT_BASE_URL",
16
+ "DNScale",
17
+ "ProtocolError",
18
+ "RateLimitError",
19
+ "Record",
20
+ "RecordType",
21
+ "Zone",
22
+ "ZoneRegion",
23
+ "ZoneStatus",
24
+ "ZoneType",
25
+ )
26
+
27
+ __version__ = "1.0.0"
@@ -0,0 +1,125 @@
1
+ """Configured DNScale API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import ssl
7
+ from collections.abc import Mapping
8
+
9
+ import httpx
10
+
11
+ from dnscale_api import AuthenticatedClient
12
+
13
+ from .errors import acheck_error_response, check_error_response
14
+ from .records import Records
15
+ from .transport import AsyncRetryTransport, RetryTransport
16
+ from .zones import Zones
17
+
18
+ DEFAULT_BASE_URL = "https://api.dnscale.eu/v1"
19
+
20
+
21
+ class DNScale:
22
+ """DNScale client with typed resources and access to every generated endpoint.
23
+
24
+ The API key is read from ``DNSCALE_API_KEY`` when it is not passed explicitly.
25
+ Only safe read requests are retried after transient HTTP or transport failures.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: str | None = None,
31
+ *,
32
+ base_url: str = DEFAULT_BASE_URL,
33
+ timeout: float | httpx.Timeout = 10.0,
34
+ max_retries: int = 2,
35
+ retry_backoff: float = 0.25,
36
+ verify_ssl: str | bool | ssl.SSLContext = True,
37
+ follow_redirects: bool = False,
38
+ headers: Mapping[str, str] | None = None,
39
+ transport: httpx.BaseTransport | None = None,
40
+ async_transport: httpx.AsyncBaseTransport | None = None,
41
+ ) -> None:
42
+ token = api_key or os.getenv("DNSCALE_API_KEY")
43
+ if not token:
44
+ raise ValueError("A DNScale API key is required; pass api_key or set DNSCALE_API_KEY")
45
+ if max_retries < 0:
46
+ raise ValueError("max_retries must be zero or greater")
47
+ if retry_backoff < 0:
48
+ raise ValueError("retry_backoff must be zero or greater")
49
+
50
+ normalized_base_url = base_url.rstrip("/")
51
+ request_headers = {
52
+ "Accept": "application/json",
53
+ "User-Agent": "dnscale-python/1.0.0",
54
+ **dict(headers or {}),
55
+ "Authorization": f"Bearer {token}",
56
+ }
57
+
58
+ sync_client = httpx.Client(
59
+ event_hooks={"response": [check_error_response]},
60
+ base_url=normalized_base_url,
61
+ headers=request_headers,
62
+ timeout=timeout,
63
+ verify=verify_ssl,
64
+ follow_redirects=follow_redirects,
65
+ transport=RetryTransport(
66
+ max_retries=max_retries,
67
+ backoff=retry_backoff,
68
+ verify_ssl=verify_ssl,
69
+ transport=transport,
70
+ ),
71
+ )
72
+ async_client = httpx.AsyncClient(
73
+ event_hooks={"response": [acheck_error_response]},
74
+ base_url=normalized_base_url,
75
+ headers=request_headers,
76
+ timeout=timeout,
77
+ verify=verify_ssl,
78
+ follow_redirects=follow_redirects,
79
+ transport=AsyncRetryTransport(
80
+ max_retries=max_retries,
81
+ backoff=retry_backoff,
82
+ verify_ssl=verify_ssl,
83
+ transport=async_transport,
84
+ ),
85
+ )
86
+
87
+ self.client = AuthenticatedClient(
88
+ base_url=normalized_base_url,
89
+ token=token,
90
+ headers=dict(request_headers),
91
+ timeout=httpx.Timeout(timeout) if isinstance(timeout, float | int) else timeout,
92
+ verify_ssl=verify_ssl,
93
+ follow_redirects=follow_redirects,
94
+ raise_on_unexpected_status=True,
95
+ )
96
+ self.client.set_httpx_client(sync_client)
97
+ self.client.set_async_httpx_client(async_client)
98
+
99
+ self.zones = Zones(self.client)
100
+ self.records = Records(self.client)
101
+
102
+ def close(self) -> None:
103
+ """Close the blocking HTTP client."""
104
+
105
+ self.client.get_httpx_client().close()
106
+
107
+ async def aclose(self) -> None:
108
+ """Close both clients, including any sync calls used in an async workflow."""
109
+
110
+ try:
111
+ await self.client.get_async_httpx_client().aclose()
112
+ finally:
113
+ self.close()
114
+
115
+ def __enter__(self) -> DNScale:
116
+ return self
117
+
118
+ def __exit__(self, *args: object) -> None:
119
+ self.close()
120
+
121
+ async def __aenter__(self) -> DNScale:
122
+ return self
123
+
124
+ async def __aexit__(self, *args: object) -> None:
125
+ await self.aclose()
@@ -0,0 +1,118 @@
1
+ """Exceptions raised by the handwritten SDK layer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, TypeVar, cast
6
+
7
+ import httpx
8
+
9
+ from dnscale_api.models.error_response import ErrorResponse
10
+ from dnscale_api.types import Response, Unset
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class APIError(Exception):
16
+ """A structured error returned by DNScale."""
17
+
18
+ def __init__(
19
+ self,
20
+ *,
21
+ status_code: int,
22
+ code: str,
23
+ message: str,
24
+ details: list[dict[str, Any]] | None = None,
25
+ request_id: str | None = None,
26
+ ) -> None:
27
+ self.status_code = status_code
28
+ self.code = code
29
+ self.message = message
30
+ self.details = details
31
+ self.request_id = request_id
32
+ super().__init__(f"DNScale API error {status_code} ({code}): {message}")
33
+
34
+
35
+ class RateLimitError(APIError):
36
+ """The API still returned HTTP 429 after safe retries were exhausted."""
37
+
38
+ def __init__(self, *, retry_after: str | None = None, **kwargs: Any) -> None:
39
+ self.retry_after = retry_after
40
+ super().__init__(**kwargs)
41
+
42
+
43
+ class ProtocolError(Exception):
44
+ """The API returned a successful response that did not match its contract."""
45
+
46
+
47
+ def _check_error_body(response: httpx.Response) -> None:
48
+ try:
49
+ body = response.json()
50
+ if (
51
+ isinstance(body, dict)
52
+ and body.get("status") == "error"
53
+ and isinstance(body.get("error"), dict)
54
+ ):
55
+ error = body["error"]
56
+ if isinstance(error.get("code"), str) and isinstance(error.get("message"), str):
57
+ return
58
+ except ValueError:
59
+ pass
60
+ kwargs: dict[str, Any] = {
61
+ "status_code": response.status_code,
62
+ "code": "HTTP_ERROR",
63
+ "message": "DNScale returned an HTTP error without a valid API error envelope",
64
+ "request_id": response.headers.get("X-Request-ID"),
65
+ }
66
+ if response.status_code == 429:
67
+ raise RateLimitError(retry_after=response.headers.get("Retry-After"), **kwargs)
68
+ raise APIError(**kwargs)
69
+
70
+
71
+ def check_error_response(response: httpx.Response) -> None:
72
+ if response.status_code >= 400:
73
+ response.read()
74
+ _check_error_body(response)
75
+
76
+
77
+ async def acheck_error_response(response: httpx.Response) -> None:
78
+ if response.status_code >= 400:
79
+ await response.aread()
80
+ _check_error_body(response)
81
+
82
+
83
+ def unwrap(response: Response[T | ErrorResponse]) -> T | None:
84
+ """Return a successful parsed response or raise a structured SDK exception."""
85
+
86
+ parsed = response.parsed
87
+ if isinstance(parsed, ErrorResponse):
88
+ raw_details = parsed.error.details
89
+ details = None
90
+ if not isinstance(raw_details, Unset):
91
+ details = [item.to_dict() for item in raw_details]
92
+
93
+ error_type = RateLimitError if int(response.status_code) == 429 else APIError
94
+ kwargs: dict[str, Any] = {
95
+ "status_code": int(response.status_code),
96
+ "code": parsed.error.code,
97
+ "message": parsed.error.message,
98
+ "details": details,
99
+ "request_id": response.headers.get("X-Request-ID"),
100
+ }
101
+ if error_type is RateLimitError:
102
+ kwargs["retry_after"] = response.headers.get("Retry-After")
103
+ raise error_type(**kwargs)
104
+
105
+ if parsed is None and not 200 <= int(response.status_code) < 300:
106
+ raise ProtocolError(
107
+ f"DNScale returned HTTP {int(response.status_code)} without a valid error body"
108
+ )
109
+
110
+ return cast(T | None, parsed)
111
+
112
+
113
+ def expect(value: T | None, expected_type: type[T]) -> T:
114
+ """Narrow generated response unions and detect successful contract drift."""
115
+
116
+ if not isinstance(value, expected_type):
117
+ raise ProtocolError(f"DNScale returned an invalid {expected_type.__name__} response")
118
+ return value