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.
- dnscale-1.0.0/GENERATION.md +24 -0
- dnscale-1.0.0/LICENSE +21 -0
- dnscale-1.0.0/MANIFEST.in +4 -0
- dnscale-1.0.0/PKG-INFO +200 -0
- dnscale-1.0.0/README.md +172 -0
- dnscale-1.0.0/dnscale/__init__.py +27 -0
- dnscale-1.0.0/dnscale/client.py +125 -0
- dnscale-1.0.0/dnscale/errors.py +118 -0
- dnscale-1.0.0/dnscale/pagination.py +20 -0
- dnscale-1.0.0/dnscale/py.typed +1 -0
- dnscale-1.0.0/dnscale/records.py +295 -0
- dnscale-1.0.0/dnscale/transport.py +129 -0
- dnscale-1.0.0/dnscale/zones.py +156 -0
- dnscale-1.0.0/dnscale.egg-info/PKG-INFO +200 -0
- dnscale-1.0.0/dnscale.egg-info/SOURCES.txt +299 -0
- dnscale-1.0.0/dnscale.egg-info/dependency_links.txt +1 -0
- dnscale-1.0.0/dnscale.egg-info/requires.txt +7 -0
- dnscale-1.0.0/dnscale.egg-info/top_level.txt +2 -0
- dnscale-1.0.0/dnscale_api/__init__.py +8 -0
- dnscale-1.0.0/dnscale_api/api/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/activity/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/activity/list_activity.py +255 -0
- dnscale-1.0.0/dnscale_api/api/alerts/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/alerts/create_alert.py +157 -0
- dnscale-1.0.0/dnscale_api/api/alerts/delete_alert.py +147 -0
- dnscale-1.0.0/dnscale_api/api/alerts/get_alert.py +151 -0
- dnscale-1.0.0/dnscale_api/api/alerts/list_alerts.py +208 -0
- dnscale-1.0.0/dnscale_api/api/alerts/update_alert.py +173 -0
- dnscale-1.0.0/dnscale_api/api/api_keys/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/api_keys/create_api_key.py +196 -0
- dnscale-1.0.0/dnscale_api/api/api_keys/delete_api_key.py +162 -0
- dnscale-1.0.0/dnscale_api/api/api_keys/get_api_key.py +168 -0
- dnscale-1.0.0/dnscale_api/api/api_keys/list_api_keys.py +154 -0
- dnscale-1.0.0/dnscale_api/api/billing/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/billing/get_billing_history.py +173 -0
- dnscale-1.0.0/dnscale_api/api/billing/get_billing_summary.py +158 -0
- dnscale-1.0.0/dnscale_api/api/billing/list_invoices.py +173 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/create_dns_control_connection.py +159 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/create_dns_control_connection_zone_snapshot.py +191 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/create_dns_control_inventory_snapshot.py +154 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/diff_dns_control_snapshots.py +182 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/get_dns_control_snapshot.py +168 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_connection_zones.py +154 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_connections.py +126 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_inventory.py +126 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_providers.py +126 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/list_dns_control_snapshots.py +190 -0
- dnscale-1.0.0/dnscale_api/api/dns_control/save_dns_control_zone_inventory.py +191 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/create_dnssec_cryptokey.py +176 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/delete_dnssec_cryptokey.py +162 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/get_dnssec_cryptokey.py +168 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/get_dnssec_status.py +154 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/list_dnssec_cryptokeys.py +154 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/list_dnssecds_records.py +154 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/set_dnssec_status.py +170 -0
- dnscale-1.0.0/dnscale_api/api/dnssec/update_dnssec_cryptokey.py +184 -0
- dnscale-1.0.0/dnscale_api/api/health/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/health/get_health.py +123 -0
- dnscale-1.0.0/dnscale_api/api/invitations/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/invitations/create_invitation.py +159 -0
- dnscale-1.0.0/dnscale_api/api/invitations/delete_invitation.py +148 -0
- dnscale-1.0.0/dnscale_api/api/invitations/get_invitation.py +154 -0
- dnscale-1.0.0/dnscale_api/api/invitations/list_invitations.py +173 -0
- dnscale-1.0.0/dnscale_api/api/records/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/records/create_record.py +176 -0
- dnscale-1.0.0/dnscale_api/api/records/delete_record.py +162 -0
- dnscale-1.0.0/dnscale_api/api/records/delete_records_by_name_and_type.py +206 -0
- dnscale-1.0.0/dnscale_api/api/records/get_record.py +168 -0
- dnscale-1.0.0/dnscale_api/api/records/list_records.py +190 -0
- dnscale-1.0.0/dnscale_api/api/records/update_record.py +198 -0
- dnscale-1.0.0/dnscale_api/api/records/update_record_by_name_and_type.py +233 -0
- dnscale-1.0.0/dnscale_api/api/usage/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/usage/get_current_usage.py +126 -0
- dnscale-1.0.0/dnscale_api/api/usage/get_daily_usage.py +199 -0
- dnscale-1.0.0/dnscale_api/api/usage/get_usage_summary.py +158 -0
- dnscale-1.0.0/dnscale_api/api/usage/get_zone_usage.py +197 -0
- dnscale-1.0.0/dnscale_api/api/usage/list_top_zones_by_usage.py +195 -0
- dnscale-1.0.0/dnscale_api/api/users/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/users/create_user.py +157 -0
- dnscale-1.0.0/dnscale_api/api/users/delete_user.py +148 -0
- dnscale-1.0.0/dnscale_api/api/users/get_user.py +152 -0
- dnscale-1.0.0/dnscale_api/api/users/list_users.py +171 -0
- dnscale-1.0.0/dnscale_api/api/users/update_user.py +178 -0
- dnscale-1.0.0/dnscale_api/api/zones/__init__.py +1 -0
- dnscale-1.0.0/dnscale_api/api/zones/create_zone.py +157 -0
- dnscale-1.0.0/dnscale_api/api/zones/delete_zone.py +148 -0
- dnscale-1.0.0/dnscale_api/api/zones/get_zone.py +152 -0
- dnscale-1.0.0/dnscale_api/api/zones/list_zones.py +171 -0
- dnscale-1.0.0/dnscale_api/api/zones/update_zone.py +178 -0
- dnscale-1.0.0/dnscale_api/client.py +268 -0
- dnscale-1.0.0/dnscale_api/errors.py +16 -0
- dnscale-1.0.0/dnscale_api/models/__init__.py +399 -0
- dnscale-1.0.0/dnscale_api/models/activity_data.py +66 -0
- dnscale-1.0.0/dnscale_api/models/activity_item.py +236 -0
- dnscale-1.0.0/dnscale_api/models/activity_item_new_values_type_0.py +46 -0
- dnscale-1.0.0/dnscale_api/models/activity_item_old_values_type_0.py +46 -0
- dnscale-1.0.0/dnscale_api/models/activity_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/activity_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/aggregated_daily_stat.py +92 -0
- dnscale-1.0.0/dnscale_api/models/aggregated_usage_response.py +100 -0
- dnscale-1.0.0/dnscale_api/models/aggregated_usage_response_envelope.py +58 -0
- dnscale-1.0.0/dnscale_api/models/aggregated_usage_response_envelope_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/alert.py +210 -0
- dnscale-1.0.0/dnscale_api/models/alert_actions.py +46 -0
- dnscale-1.0.0/dnscale_api/models/alert_channel.py +125 -0
- dnscale-1.0.0/dnscale_api/models/alert_channel_type.py +11 -0
- dnscale-1.0.0/dnscale_api/models/alert_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/alert_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/alert_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/alert_scope_type.py +9 -0
- dnscale-1.0.0/dnscale_api/models/alert_severity.py +10 -0
- dnscale-1.0.0/dnscale_api/models/alert_status.py +9 -0
- dnscale-1.0.0/dnscale_api/models/alert_threshold_config.py +114 -0
- dnscale-1.0.0/dnscale_api/models/alerts_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/alerts_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/alerts_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/api_key.py +172 -0
- dnscale-1.0.0/dnscale_api/models/api_key_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/api_key_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/api_key_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/api_key_zone_scope.py +66 -0
- dnscale-1.0.0/dnscale_api/models/api_key_zone_scope_record_access.py +9 -0
- dnscale-1.0.0/dnscale_api/models/api_keys_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/api_keys_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/api_keys_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/billing_history.py +64 -0
- dnscale-1.0.0/dnscale_api/models/billing_history_response_envelope.py +58 -0
- dnscale-1.0.0/dnscale_api/models/billing_history_response_envelope_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/billing_summary.py +127 -0
- dnscale-1.0.0/dnscale_api/models/billing_summary_response_envelope.py +58 -0
- dnscale-1.0.0/dnscale_api/models/billing_summary_response_envelope_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/create_alert_request.py +162 -0
- dnscale-1.0.0/dnscale_api/models/create_alert_request_actions.py +46 -0
- dnscale-1.0.0/dnscale_api/models/create_api_key_request.py +140 -0
- dnscale-1.0.0/dnscale_api/models/create_cryptokey_request.py +135 -0
- dnscale-1.0.0/dnscale_api/models/create_cryptokey_request_keytype.py +10 -0
- dnscale-1.0.0/dnscale_api/models/create_dns_control_connection_request.py +144 -0
- dnscale-1.0.0/dnscale_api/models/create_dns_control_connection_request_mode.py +8 -0
- dnscale-1.0.0/dnscale_api/models/create_dns_control_connection_request_provider.py +9 -0
- dnscale-1.0.0/dnscale_api/models/create_invitation_request.py +42 -0
- dnscale-1.0.0/dnscale_api/models/create_record_request.py +119 -0
- dnscale-1.0.0/dnscale_api/models/create_user_request.py +79 -0
- dnscale-1.0.0/dnscale_api/models/create_zone_request.py +95 -0
- dnscale-1.0.0/dnscale_api/models/cryptokey.py +127 -0
- dnscale-1.0.0/dnscale_api/models/cryptokey_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/cryptokey_keytype.py +10 -0
- dnscale-1.0.0/dnscale_api/models/cryptokey_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/cryptokey_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/cryptokeys_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/cryptokeys_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/cryptokeys_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/customer_usage_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/customer_usage_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/customer_usage_summary.py +191 -0
- dnscale-1.0.0/dnscale_api/models/customer_usage_summary_billing_type.py +9 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_auth_field.py +87 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_auth_field_type.py +10 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection.py +209 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_mode.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_snapshot_data.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_snapshot_response.py +60 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_snapshot_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connection_status.py +11 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connections_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connections_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_connections_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory.py +149 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_delegation.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_dnssec.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_list_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_list_response.py +60 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_list_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_inventory_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider.py +121 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_auth_type.py +11 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_capabilities.py +135 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_type.py +9 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone.py +121 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone_dnssec.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone_metadata.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zone_request.py +42 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zones_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zones_response.py +60 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_provider_zones_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_providers_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_providers_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_providers_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_record_set.py +191 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_record_set_provider_metadata.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_record_set_raw_refs_item.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_record_set_values_item.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot.py +141 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_canonical_zone.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff.py +83 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change.py +133 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_after.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_before.py +46 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_severity.py +10 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_change_type.py +12 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_diff_summary.py +82 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshot_source_kind.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshots_data.py +66 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshots_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dns_control_snapshots_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/dnssec_status_data.py +42 -0
- dnscale-1.0.0/dnscale_api/models/dnssec_status_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/dnssec_status_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/ds_records_data.py +42 -0
- dnscale-1.0.0/dnscale_api/models/ds_records_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/ds_records_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/error_detail.py +79 -0
- dnscale-1.0.0/dnscale_api/models/error_detail_details_item.py +46 -0
- dnscale-1.0.0/dnscale_api/models/error_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/error_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/health.py +62 -0
- dnscale-1.0.0/dnscale_api/models/health_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/health_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/health_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/health_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/invitation.py +113 -0
- dnscale-1.0.0/dnscale_api/models/invitation_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/invitation_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/invitation_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/invitation_status.py +11 -0
- dnscale-1.0.0/dnscale_api/models/invitations_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/invitations_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/invitations_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/invoice.py +281 -0
- dnscale-1.0.0/dnscale_api/models/invoices_data.py +64 -0
- dnscale-1.0.0/dnscale_api/models/invoices_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/invoices_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/offset_meta.py +50 -0
- dnscale-1.0.0/dnscale_api/models/pagination.py +74 -0
- dnscale-1.0.0/dnscale_api/models/provider_credentials.py +79 -0
- dnscale-1.0.0/dnscale_api/models/record.py +125 -0
- dnscale-1.0.0/dnscale_api/models/record_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/record_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/record_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/record_type.py +22 -0
- dnscale-1.0.0/dnscale_api/models/records_data.py +66 -0
- dnscale-1.0.0/dnscale_api/models/records_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/records_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/set_dnssec_request.py +42 -0
- dnscale-1.0.0/dnscale_api/models/top_zones_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/top_zones_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/top_zones_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/update_alert_request.py +176 -0
- dnscale-1.0.0/dnscale_api/models/update_alert_request_actions.py +46 -0
- dnscale-1.0.0/dnscale_api/models/update_cryptokey_request.py +51 -0
- dnscale-1.0.0/dnscale_api/models/update_record_by_name_request.py +102 -0
- dnscale-1.0.0/dnscale_api/models/update_user_request.py +80 -0
- dnscale-1.0.0/dnscale_api/models/update_zone_request.py +96 -0
- dnscale-1.0.0/dnscale_api/models/usage_map.py +46 -0
- dnscale-1.0.0/dnscale_api/models/user.py +124 -0
- dnscale-1.0.0/dnscale_api/models/user_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/user_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/user_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/user_role.py +9 -0
- dnscale-1.0.0/dnscale_api/models/users_data.py +56 -0
- dnscale-1.0.0/dnscale_api/models/users_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/users_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/zone.py +128 -0
- dnscale-1.0.0/dnscale_api/models/zone_data.py +48 -0
- dnscale-1.0.0/dnscale_api/models/zone_region.py +10 -0
- dnscale-1.0.0/dnscale_api/models/zone_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/zone_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/zone_settings_brief.py +42 -0
- dnscale-1.0.0/dnscale_api/models/zone_status.py +11 -0
- dnscale-1.0.0/dnscale_api/models/zone_type.py +9 -0
- dnscale-1.0.0/dnscale_api/models/zone_usage_daily.py +163 -0
- dnscale-1.0.0/dnscale_api/models/zone_usage_response.py +58 -0
- dnscale-1.0.0/dnscale_api/models/zone_usage_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/models/zone_usage_summary.py +128 -0
- dnscale-1.0.0/dnscale_api/models/zones_data.py +66 -0
- dnscale-1.0.0/dnscale_api/models/zones_response.py +102 -0
- dnscale-1.0.0/dnscale_api/models/zones_response_status.py +8 -0
- dnscale-1.0.0/dnscale_api/py.typed +1 -0
- dnscale-1.0.0/dnscale_api/types.py +54 -0
- dnscale-1.0.0/generator.yaml +6 -0
- dnscale-1.0.0/openapi.yaml +3418 -0
- dnscale-1.0.0/pyproject.toml +60 -0
- dnscale-1.0.0/scripts/generate.sh +8 -0
- dnscale-1.0.0/setup.cfg +4 -0
- dnscale-1.0.0/tests/test_api_key_scope.py +31 -0
- dnscale-1.0.0/tests/test_client.py +175 -0
- dnscale-1.0.0/tests/test_distribution.py +22 -0
- dnscale-1.0.0/tests/test_live.py +28 -0
- 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.
|
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).
|
dnscale-1.0.0/README.md
ADDED
|
@@ -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
|