diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6db19b9..4ad3fef 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.17.0" + ".": "0.18.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 576e5df..f1de74b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 40 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-628db0b9b7c9da594fa6ad6ce9d95f4ecad92c9e0313f2f1f9977216494dbc5d.yml -openapi_spec_hash: 1773341fbff31b84d2cbcdb37eaad877 -config_hash: b090c2bdd7a719c56c825edddc587737 +configured_endpoints: 58 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-ee4b9d190e3aaa146b08bc0ffed1c802dc353c3fdc37fc0097f2350ab3714b70.yml +openapi_spec_hash: 0dad8b2e562ba7ce879425ab92169d85 +config_hash: b70b11b10fc614f91f1c6f028b40780f diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a605d7..b1e8e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.18.0 (2026-02-05) + +Full Changelog: [v0.17.0...v0.18.0](https://github.com/ArkHQ-io/ark-python/compare/v0.17.0...v0.18.0) + +### Features + +* **api:** add Credentials endpoint ([0ff55ca](https://github.com/ArkHQ-io/ark-python/commit/0ff55caabeb38ad0046cf489c28f8dad3caddfc2)) +* **api:** add Platform webhooks ([f0a53a7](https://github.com/ArkHQ-io/ark-python/commit/f0a53a79606bece5fd5cf9b2dad7a580b8bae94e)) +* **api:** endpoint updates ([0e54a04](https://github.com/ArkHQ-io/ark-python/commit/0e54a042195b1134b7c5cba9ee2ca4b98f35b361)) +* **api:** standardization improvements ([b52928d](https://github.com/ArkHQ-io/ark-python/commit/b52928d6147770b7c3a68001dad6d3861ff43967)) +* **api:** tenant usage ([09a4d02](https://github.com/ArkHQ-io/ark-python/commit/09a4d02d5acdb3b2a20ca62930e7c644bc5968c9)) + ## 0.17.0 (2026-02-03) Full Changelog: [v0.16.0...v0.17.0](https://github.com/ArkHQ-io/ark-python/compare/v0.16.0...v0.17.0) diff --git a/README.md b/README.md index bcf106d..b51d0b6 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ response = client.emails.send( to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -81,7 +81,7 @@ async def main() -> None: to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -125,7 +125,7 @@ async def main() -> None: to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -242,7 +242,7 @@ try: to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -295,7 +295,7 @@ client.with_options(max_retries=5).emails.send( to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -328,7 +328,7 @@ client.with_options(timeout=5.0).emails.send( to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -379,7 +379,7 @@ response = client.emails.with_raw_response.send( to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", @@ -407,7 +407,7 @@ with client.emails.with_streaming_response.send( to=["user@example.com"], html="

Welcome!

", metadata={ - "user_id": "usr_123", + "user_id": "usr_123456", "campaign": "onboarding", }, tag="welcome", diff --git a/api.md b/api.md index 19e5c9f..65acd82 100644 --- a/api.md +++ b/api.md @@ -22,20 +22,110 @@ from ark.types import ( Methods: -- client.emails.retrieve(id, \*\*params) -> EmailRetrieveResponse +- client.emails.retrieve(email_id, \*\*params) -> EmailRetrieveResponse - client.emails.list(\*\*params) -> SyncPageNumberPagination[EmailListResponse] -- client.emails.retrieve_deliveries(id) -> EmailRetrieveDeliveriesResponse -- client.emails.retry(id) -> EmailRetryResponse +- client.emails.retrieve_deliveries(email_id) -> EmailRetrieveDeliveriesResponse +- client.emails.retry(email_id) -> EmailRetryResponse - client.emails.send(\*\*params) -> EmailSendResponse - client.emails.send_batch(\*\*params) -> EmailSendBatchResponse - client.emails.send_raw(\*\*params) -> EmailSendRawResponse -# Domains +# Logs + +Types: + +```python +from ark.types import LogEntry, LogEntryDetail, LogRetrieveResponse +``` + +Methods: + +- client.logs.retrieve(request_id) -> LogRetrieveResponse +- client.logs.list(\*\*params) -> SyncPageNumberPagination[LogEntry] + +# Usage Types: ```python from ark.types import ( + EmailCounts, + EmailRates, + OrgUsageSummary, + TenantUsageItem, + UsagePeriod, + UsageExportResponse, +) +``` + +Methods: + +- client.usage.retrieve(\*\*params) -> OrgUsageSummary +- client.usage.export(\*\*params) -> UsageExportResponse +- client.usage.list_tenants(\*\*params) -> SyncPageNumberPagination[TenantUsageItem] + +# Limits + +Types: + +```python +from ark.types import LimitsData, LimitRetrieveResponse +``` + +Methods: + +- client.limits.retrieve() -> LimitRetrieveResponse + +# Tenants + +Types: + +```python +from ark.types import ( + Tenant, + TenantCreateResponse, + TenantRetrieveResponse, + TenantUpdateResponse, + TenantDeleteResponse, +) +``` + +Methods: + +- client.tenants.create(\*\*params) -> TenantCreateResponse +- client.tenants.retrieve(tenant_id) -> TenantRetrieveResponse +- client.tenants.update(tenant_id, \*\*params) -> TenantUpdateResponse +- client.tenants.list(\*\*params) -> SyncPageNumberPagination[Tenant] +- client.tenants.delete(tenant_id) -> TenantDeleteResponse + +## Credentials + +Types: + +```python +from ark.types.tenants import ( + CredentialCreateResponse, + CredentialRetrieveResponse, + CredentialUpdateResponse, + CredentialListResponse, + CredentialDeleteResponse, +) +``` + +Methods: + +- client.tenants.credentials.create(tenant_id, \*\*params) -> CredentialCreateResponse +- client.tenants.credentials.retrieve(credential_id, \*, tenant_id, \*\*params) -> CredentialRetrieveResponse +- client.tenants.credentials.update(credential_id, \*, tenant_id, \*\*params) -> CredentialUpdateResponse +- client.tenants.credentials.list(tenant_id, \*\*params) -> SyncPageNumberPagination[CredentialListResponse] +- client.tenants.credentials.delete(credential_id, \*, tenant_id) -> CredentialDeleteResponse + +## Domains + +Types: + +```python +from ark.types.tenants import ( DNSRecord, DomainCreateResponse, DomainRetrieveResponse, @@ -47,40 +137,38 @@ from ark.types import ( Methods: -- client.domains.create(\*\*params) -> DomainCreateResponse -- client.domains.retrieve(domain_id) -> DomainRetrieveResponse -- client.domains.list() -> DomainListResponse -- client.domains.delete(domain_id) -> DomainDeleteResponse -- client.domains.verify(domain_id) -> DomainVerifyResponse +- client.tenants.domains.create(tenant_id, \*\*params) -> DomainCreateResponse +- client.tenants.domains.retrieve(domain_id, \*, tenant_id) -> DomainRetrieveResponse +- client.tenants.domains.list(tenant_id) -> DomainListResponse +- client.tenants.domains.delete(domain_id, \*, tenant_id) -> DomainDeleteResponse +- client.tenants.domains.verify(domain_id, \*, tenant_id) -> DomainVerifyResponse -# Suppressions +## Suppressions Types: ```python -from ark.types import ( +from ark.types.tenants import ( SuppressionCreateResponse, SuppressionRetrieveResponse, SuppressionListResponse, SuppressionDeleteResponse, - SuppressionBulkCreateResponse, ) ``` Methods: -- client.suppressions.create(\*\*params) -> SuppressionCreateResponse -- client.suppressions.retrieve(email) -> SuppressionRetrieveResponse -- client.suppressions.list(\*\*params) -> SyncPageNumberPagination[SuppressionListResponse] -- client.suppressions.delete(email) -> SuppressionDeleteResponse -- client.suppressions.bulk_create(\*\*params) -> SuppressionBulkCreateResponse +- client.tenants.suppressions.create(tenant_id, \*\*params) -> SuppressionCreateResponse +- client.tenants.suppressions.retrieve(email, \*, tenant_id) -> SuppressionRetrieveResponse +- client.tenants.suppressions.list(tenant_id, \*\*params) -> SyncPageNumberPagination[SuppressionListResponse] +- client.tenants.suppressions.delete(email, \*, tenant_id) -> SuppressionDeleteResponse -# Webhooks +## Webhooks Types: ```python -from ark.types import ( +from ark.types.tenants import ( WebhookCreateResponse, WebhookRetrieveResponse, WebhookUpdateResponse, @@ -95,22 +183,22 @@ from ark.types import ( Methods: -- client.webhooks.create(\*\*params) -> WebhookCreateResponse -- client.webhooks.retrieve(webhook_id) -> WebhookRetrieveResponse -- client.webhooks.update(webhook_id, \*\*params) -> WebhookUpdateResponse -- client.webhooks.list() -> WebhookListResponse -- client.webhooks.delete(webhook_id) -> WebhookDeleteResponse -- client.webhooks.list_deliveries(webhook_id, \*\*params) -> WebhookListDeliveriesResponse -- client.webhooks.replay_delivery(delivery_id, \*, webhook_id) -> WebhookReplayDeliveryResponse -- client.webhooks.retrieve_delivery(delivery_id, \*, webhook_id) -> WebhookRetrieveDeliveryResponse -- client.webhooks.test(webhook_id, \*\*params) -> WebhookTestResponse +- client.tenants.webhooks.create(tenant_id, \*\*params) -> WebhookCreateResponse +- client.tenants.webhooks.retrieve(webhook_id, \*, tenant_id) -> WebhookRetrieveResponse +- client.tenants.webhooks.update(webhook_id, \*, tenant_id, \*\*params) -> WebhookUpdateResponse +- client.tenants.webhooks.list(tenant_id) -> WebhookListResponse +- client.tenants.webhooks.delete(webhook_id, \*, tenant_id) -> WebhookDeleteResponse +- client.tenants.webhooks.list_deliveries(webhook_id, \*, tenant_id, \*\*params) -> WebhookListDeliveriesResponse +- client.tenants.webhooks.replay_delivery(delivery_id, \*, tenant_id, webhook_id) -> WebhookReplayDeliveryResponse +- client.tenants.webhooks.retrieve_delivery(delivery_id, \*, tenant_id, webhook_id) -> WebhookRetrieveDeliveryResponse +- client.tenants.webhooks.test(webhook_id, \*, tenant_id, \*\*params) -> WebhookTestResponse -# Tracking +## Tracking Types: ```python -from ark.types import ( +from ark.types.tenants import ( TrackDomain, TrackingCreateResponse, TrackingRetrieveResponse, @@ -123,56 +211,59 @@ from ark.types import ( Methods: -- client.tracking.create(\*\*params) -> TrackingCreateResponse -- client.tracking.retrieve(tracking_id) -> TrackingRetrieveResponse -- client.tracking.update(tracking_id, \*\*params) -> TrackingUpdateResponse -- client.tracking.list() -> TrackingListResponse -- client.tracking.delete(tracking_id) -> TrackingDeleteResponse -- client.tracking.verify(tracking_id) -> TrackingVerifyResponse +- client.tenants.tracking.create(tenant_id, \*\*params) -> TrackingCreateResponse +- client.tenants.tracking.retrieve(tracking_id, \*, tenant_id) -> TrackingRetrieveResponse +- client.tenants.tracking.update(tracking_id, \*, tenant_id, \*\*params) -> TrackingUpdateResponse +- client.tenants.tracking.list(tenant_id) -> TrackingListResponse +- client.tenants.tracking.delete(tracking_id, \*, tenant_id) -> TrackingDeleteResponse +- client.tenants.tracking.verify(tracking_id, \*, tenant_id) -> TrackingVerifyResponse -# Logs +## Usage Types: ```python -from ark.types import LogEntry, LogEntryDetail, LogRetrieveResponse +from ark.types.tenants import ( + TenantUsage, + TenantUsageTimeseries, + UsageRetrieveResponse, + UsageRetrieveTimeseriesResponse, +) ``` Methods: -- client.logs.retrieve(request_id) -> LogRetrieveResponse -- client.logs.list(\*\*params) -> SyncPageNumberPagination[LogEntry] - -# Usage - -Types: - -```python -from ark.types import UsageRetrieveResponse -``` - -Methods: +- client.tenants.usage.retrieve(tenant_id, \*\*params) -> UsageRetrieveResponse +- client.tenants.usage.retrieve_timeseries(tenant_id, \*\*params) -> UsageRetrieveTimeseriesResponse -- client.usage.retrieve() -> UsageRetrieveResponse +# Platform -# Tenants +## Webhooks Types: ```python -from ark.types import ( - Tenant, - TenantCreateResponse, - TenantRetrieveResponse, - TenantUpdateResponse, - TenantDeleteResponse, +from ark.types.platform import ( + WebhookCreateResponse, + WebhookRetrieveResponse, + WebhookUpdateResponse, + WebhookListResponse, + WebhookDeleteResponse, + WebhookListDeliveriesResponse, + WebhookReplayDeliveryResponse, + WebhookRetrieveDeliveryResponse, + WebhookTestResponse, ) ``` Methods: -- client.tenants.create(\*\*params) -> TenantCreateResponse -- client.tenants.retrieve(tenant_id) -> TenantRetrieveResponse -- client.tenants.update(tenant_id, \*\*params) -> TenantUpdateResponse -- client.tenants.list(\*\*params) -> SyncPageNumberPagination[Tenant] -- client.tenants.delete(tenant_id) -> TenantDeleteResponse +- client.platform.webhooks.create(\*\*params) -> WebhookCreateResponse +- client.platform.webhooks.retrieve(webhook_id) -> WebhookRetrieveResponse +- client.platform.webhooks.update(webhook_id, \*\*params) -> WebhookUpdateResponse +- client.platform.webhooks.list() -> WebhookListResponse +- client.platform.webhooks.delete(webhook_id) -> WebhookDeleteResponse +- client.platform.webhooks.list_deliveries(\*\*params) -> SyncPageNumberPagination[WebhookListDeliveriesResponse] +- client.platform.webhooks.replay_delivery(delivery_id) -> WebhookReplayDeliveryResponse +- client.platform.webhooks.retrieve_delivery(delivery_id) -> WebhookRetrieveDeliveryResponse +- client.platform.webhooks.test(webhook_id, \*\*params) -> WebhookTestResponse diff --git a/pyproject.toml b/pyproject.toml index 1fc344a..8c5da08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ark-email" -version = "0.17.0" +version = "0.18.0" description = "The official Python library for the ark API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/ark/_client.py b/src/ark/_client.py index 51fb70d..40de787 100644 --- a/src/ark/_client.py +++ b/src/ark/_client.py @@ -31,15 +31,13 @@ ) if TYPE_CHECKING: - from .resources import logs, usage, emails, domains, tenants, tracking, webhooks, suppressions + from .resources import logs, usage, emails, limits, tenants, platform from .resources.logs import LogsResource, AsyncLogsResource from .resources.usage import UsageResource, AsyncUsageResource from .resources.emails import EmailsResource, AsyncEmailsResource - from .resources.domains import DomainsResource, AsyncDomainsResource - from .resources.tenants import TenantsResource, AsyncTenantsResource - from .resources.tracking import TrackingResource, AsyncTrackingResource - from .resources.webhooks import WebhooksResource, AsyncWebhooksResource - from .resources.suppressions import SuppressionsResource, AsyncSuppressionsResource + from .resources.limits import LimitsResource, AsyncLimitsResource + from .resources.tenants.tenants import TenantsResource, AsyncTenantsResource + from .resources.platform.platform import PlatformResource, AsyncPlatformResource __all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Ark", "AsyncArk", "Client", "AsyncClient"] @@ -105,30 +103,6 @@ def emails(self) -> EmailsResource: return EmailsResource(self) - @cached_property - def domains(self) -> DomainsResource: - from .resources.domains import DomainsResource - - return DomainsResource(self) - - @cached_property - def suppressions(self) -> SuppressionsResource: - from .resources.suppressions import SuppressionsResource - - return SuppressionsResource(self) - - @cached_property - def webhooks(self) -> WebhooksResource: - from .resources.webhooks import WebhooksResource - - return WebhooksResource(self) - - @cached_property - def tracking(self) -> TrackingResource: - from .resources.tracking import TrackingResource - - return TrackingResource(self) - @cached_property def logs(self) -> LogsResource: from .resources.logs import LogsResource @@ -141,12 +115,24 @@ def usage(self) -> UsageResource: return UsageResource(self) + @cached_property + def limits(self) -> LimitsResource: + from .resources.limits import LimitsResource + + return LimitsResource(self) + @cached_property def tenants(self) -> TenantsResource: from .resources.tenants import TenantsResource return TenantsResource(self) + @cached_property + def platform(self) -> PlatformResource: + from .resources.platform import PlatformResource + + return PlatformResource(self) + @cached_property def with_raw_response(self) -> ArkWithRawResponse: return ArkWithRawResponse(self) @@ -321,30 +307,6 @@ def emails(self) -> AsyncEmailsResource: return AsyncEmailsResource(self) - @cached_property - def domains(self) -> AsyncDomainsResource: - from .resources.domains import AsyncDomainsResource - - return AsyncDomainsResource(self) - - @cached_property - def suppressions(self) -> AsyncSuppressionsResource: - from .resources.suppressions import AsyncSuppressionsResource - - return AsyncSuppressionsResource(self) - - @cached_property - def webhooks(self) -> AsyncWebhooksResource: - from .resources.webhooks import AsyncWebhooksResource - - return AsyncWebhooksResource(self) - - @cached_property - def tracking(self) -> AsyncTrackingResource: - from .resources.tracking import AsyncTrackingResource - - return AsyncTrackingResource(self) - @cached_property def logs(self) -> AsyncLogsResource: from .resources.logs import AsyncLogsResource @@ -357,12 +319,24 @@ def usage(self) -> AsyncUsageResource: return AsyncUsageResource(self) + @cached_property + def limits(self) -> AsyncLimitsResource: + from .resources.limits import AsyncLimitsResource + + return AsyncLimitsResource(self) + @cached_property def tenants(self) -> AsyncTenantsResource: from .resources.tenants import AsyncTenantsResource return AsyncTenantsResource(self) + @cached_property + def platform(self) -> AsyncPlatformResource: + from .resources.platform import AsyncPlatformResource + + return AsyncPlatformResource(self) + @cached_property def with_raw_response(self) -> AsyncArkWithRawResponse: return AsyncArkWithRawResponse(self) @@ -488,30 +462,6 @@ def emails(self) -> emails.EmailsResourceWithRawResponse: return EmailsResourceWithRawResponse(self._client.emails) - @cached_property - def domains(self) -> domains.DomainsResourceWithRawResponse: - from .resources.domains import DomainsResourceWithRawResponse - - return DomainsResourceWithRawResponse(self._client.domains) - - @cached_property - def suppressions(self) -> suppressions.SuppressionsResourceWithRawResponse: - from .resources.suppressions import SuppressionsResourceWithRawResponse - - return SuppressionsResourceWithRawResponse(self._client.suppressions) - - @cached_property - def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse: - from .resources.webhooks import WebhooksResourceWithRawResponse - - return WebhooksResourceWithRawResponse(self._client.webhooks) - - @cached_property - def tracking(self) -> tracking.TrackingResourceWithRawResponse: - from .resources.tracking import TrackingResourceWithRawResponse - - return TrackingResourceWithRawResponse(self._client.tracking) - @cached_property def logs(self) -> logs.LogsResourceWithRawResponse: from .resources.logs import LogsResourceWithRawResponse @@ -524,12 +474,24 @@ def usage(self) -> usage.UsageResourceWithRawResponse: return UsageResourceWithRawResponse(self._client.usage) + @cached_property + def limits(self) -> limits.LimitsResourceWithRawResponse: + from .resources.limits import LimitsResourceWithRawResponse + + return LimitsResourceWithRawResponse(self._client.limits) + @cached_property def tenants(self) -> tenants.TenantsResourceWithRawResponse: from .resources.tenants import TenantsResourceWithRawResponse return TenantsResourceWithRawResponse(self._client.tenants) + @cached_property + def platform(self) -> platform.PlatformResourceWithRawResponse: + from .resources.platform import PlatformResourceWithRawResponse + + return PlatformResourceWithRawResponse(self._client.platform) + class AsyncArkWithRawResponse: _client: AsyncArk @@ -543,30 +505,6 @@ def emails(self) -> emails.AsyncEmailsResourceWithRawResponse: return AsyncEmailsResourceWithRawResponse(self._client.emails) - @cached_property - def domains(self) -> domains.AsyncDomainsResourceWithRawResponse: - from .resources.domains import AsyncDomainsResourceWithRawResponse - - return AsyncDomainsResourceWithRawResponse(self._client.domains) - - @cached_property - def suppressions(self) -> suppressions.AsyncSuppressionsResourceWithRawResponse: - from .resources.suppressions import AsyncSuppressionsResourceWithRawResponse - - return AsyncSuppressionsResourceWithRawResponse(self._client.suppressions) - - @cached_property - def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse: - from .resources.webhooks import AsyncWebhooksResourceWithRawResponse - - return AsyncWebhooksResourceWithRawResponse(self._client.webhooks) - - @cached_property - def tracking(self) -> tracking.AsyncTrackingResourceWithRawResponse: - from .resources.tracking import AsyncTrackingResourceWithRawResponse - - return AsyncTrackingResourceWithRawResponse(self._client.tracking) - @cached_property def logs(self) -> logs.AsyncLogsResourceWithRawResponse: from .resources.logs import AsyncLogsResourceWithRawResponse @@ -579,12 +517,24 @@ def usage(self) -> usage.AsyncUsageResourceWithRawResponse: return AsyncUsageResourceWithRawResponse(self._client.usage) + @cached_property + def limits(self) -> limits.AsyncLimitsResourceWithRawResponse: + from .resources.limits import AsyncLimitsResourceWithRawResponse + + return AsyncLimitsResourceWithRawResponse(self._client.limits) + @cached_property def tenants(self) -> tenants.AsyncTenantsResourceWithRawResponse: from .resources.tenants import AsyncTenantsResourceWithRawResponse return AsyncTenantsResourceWithRawResponse(self._client.tenants) + @cached_property + def platform(self) -> platform.AsyncPlatformResourceWithRawResponse: + from .resources.platform import AsyncPlatformResourceWithRawResponse + + return AsyncPlatformResourceWithRawResponse(self._client.platform) + class ArkWithStreamedResponse: _client: Ark @@ -598,30 +548,6 @@ def emails(self) -> emails.EmailsResourceWithStreamingResponse: return EmailsResourceWithStreamingResponse(self._client.emails) - @cached_property - def domains(self) -> domains.DomainsResourceWithStreamingResponse: - from .resources.domains import DomainsResourceWithStreamingResponse - - return DomainsResourceWithStreamingResponse(self._client.domains) - - @cached_property - def suppressions(self) -> suppressions.SuppressionsResourceWithStreamingResponse: - from .resources.suppressions import SuppressionsResourceWithStreamingResponse - - return SuppressionsResourceWithStreamingResponse(self._client.suppressions) - - @cached_property - def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse: - from .resources.webhooks import WebhooksResourceWithStreamingResponse - - return WebhooksResourceWithStreamingResponse(self._client.webhooks) - - @cached_property - def tracking(self) -> tracking.TrackingResourceWithStreamingResponse: - from .resources.tracking import TrackingResourceWithStreamingResponse - - return TrackingResourceWithStreamingResponse(self._client.tracking) - @cached_property def logs(self) -> logs.LogsResourceWithStreamingResponse: from .resources.logs import LogsResourceWithStreamingResponse @@ -634,12 +560,24 @@ def usage(self) -> usage.UsageResourceWithStreamingResponse: return UsageResourceWithStreamingResponse(self._client.usage) + @cached_property + def limits(self) -> limits.LimitsResourceWithStreamingResponse: + from .resources.limits import LimitsResourceWithStreamingResponse + + return LimitsResourceWithStreamingResponse(self._client.limits) + @cached_property def tenants(self) -> tenants.TenantsResourceWithStreamingResponse: from .resources.tenants import TenantsResourceWithStreamingResponse return TenantsResourceWithStreamingResponse(self._client.tenants) + @cached_property + def platform(self) -> platform.PlatformResourceWithStreamingResponse: + from .resources.platform import PlatformResourceWithStreamingResponse + + return PlatformResourceWithStreamingResponse(self._client.platform) + class AsyncArkWithStreamedResponse: _client: AsyncArk @@ -653,30 +591,6 @@ def emails(self) -> emails.AsyncEmailsResourceWithStreamingResponse: return AsyncEmailsResourceWithStreamingResponse(self._client.emails) - @cached_property - def domains(self) -> domains.AsyncDomainsResourceWithStreamingResponse: - from .resources.domains import AsyncDomainsResourceWithStreamingResponse - - return AsyncDomainsResourceWithStreamingResponse(self._client.domains) - - @cached_property - def suppressions(self) -> suppressions.AsyncSuppressionsResourceWithStreamingResponse: - from .resources.suppressions import AsyncSuppressionsResourceWithStreamingResponse - - return AsyncSuppressionsResourceWithStreamingResponse(self._client.suppressions) - - @cached_property - def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse: - from .resources.webhooks import AsyncWebhooksResourceWithStreamingResponse - - return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks) - - @cached_property - def tracking(self) -> tracking.AsyncTrackingResourceWithStreamingResponse: - from .resources.tracking import AsyncTrackingResourceWithStreamingResponse - - return AsyncTrackingResourceWithStreamingResponse(self._client.tracking) - @cached_property def logs(self) -> logs.AsyncLogsResourceWithStreamingResponse: from .resources.logs import AsyncLogsResourceWithStreamingResponse @@ -689,12 +603,24 @@ def usage(self) -> usage.AsyncUsageResourceWithStreamingResponse: return AsyncUsageResourceWithStreamingResponse(self._client.usage) + @cached_property + def limits(self) -> limits.AsyncLimitsResourceWithStreamingResponse: + from .resources.limits import AsyncLimitsResourceWithStreamingResponse + + return AsyncLimitsResourceWithStreamingResponse(self._client.limits) + @cached_property def tenants(self) -> tenants.AsyncTenantsResourceWithStreamingResponse: from .resources.tenants import AsyncTenantsResourceWithStreamingResponse return AsyncTenantsResourceWithStreamingResponse(self._client.tenants) + @cached_property + def platform(self) -> platform.AsyncPlatformResourceWithStreamingResponse: + from .resources.platform import AsyncPlatformResourceWithStreamingResponse + + return AsyncPlatformResourceWithStreamingResponse(self._client.platform) + Client = Ark diff --git a/src/ark/_version.py b/src/ark/_version.py index 8bd9d9e..53cb1de 100644 --- a/src/ark/_version.py +++ b/src/ark/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "ark" -__version__ = "0.17.0" # x-release-please-version +__version__ = "0.18.0" # x-release-please-version diff --git a/src/ark/resources/__init__.py b/src/ark/resources/__init__.py index f3812e4..9908f11 100644 --- a/src/ark/resources/__init__.py +++ b/src/ark/resources/__init__.py @@ -24,13 +24,13 @@ EmailsResourceWithStreamingResponse, AsyncEmailsResourceWithStreamingResponse, ) -from .domains import ( - DomainsResource, - AsyncDomainsResource, - DomainsResourceWithRawResponse, - AsyncDomainsResourceWithRawResponse, - DomainsResourceWithStreamingResponse, - AsyncDomainsResourceWithStreamingResponse, +from .limits import ( + LimitsResource, + AsyncLimitsResource, + LimitsResourceWithRawResponse, + AsyncLimitsResourceWithRawResponse, + LimitsResourceWithStreamingResponse, + AsyncLimitsResourceWithStreamingResponse, ) from .tenants import ( TenantsResource, @@ -40,29 +40,13 @@ TenantsResourceWithStreamingResponse, AsyncTenantsResourceWithStreamingResponse, ) -from .tracking import ( - TrackingResource, - AsyncTrackingResource, - TrackingResourceWithRawResponse, - AsyncTrackingResourceWithRawResponse, - TrackingResourceWithStreamingResponse, - AsyncTrackingResourceWithStreamingResponse, -) -from .webhooks import ( - WebhooksResource, - AsyncWebhooksResource, - WebhooksResourceWithRawResponse, - AsyncWebhooksResourceWithRawResponse, - WebhooksResourceWithStreamingResponse, - AsyncWebhooksResourceWithStreamingResponse, -) -from .suppressions import ( - SuppressionsResource, - AsyncSuppressionsResource, - SuppressionsResourceWithRawResponse, - AsyncSuppressionsResourceWithRawResponse, - SuppressionsResourceWithStreamingResponse, - AsyncSuppressionsResourceWithStreamingResponse, +from .platform import ( + PlatformResource, + AsyncPlatformResource, + PlatformResourceWithRawResponse, + AsyncPlatformResourceWithRawResponse, + PlatformResourceWithStreamingResponse, + AsyncPlatformResourceWithStreamingResponse, ) __all__ = [ @@ -72,30 +56,6 @@ "AsyncEmailsResourceWithRawResponse", "EmailsResourceWithStreamingResponse", "AsyncEmailsResourceWithStreamingResponse", - "DomainsResource", - "AsyncDomainsResource", - "DomainsResourceWithRawResponse", - "AsyncDomainsResourceWithRawResponse", - "DomainsResourceWithStreamingResponse", - "AsyncDomainsResourceWithStreamingResponse", - "SuppressionsResource", - "AsyncSuppressionsResource", - "SuppressionsResourceWithRawResponse", - "AsyncSuppressionsResourceWithRawResponse", - "SuppressionsResourceWithStreamingResponse", - "AsyncSuppressionsResourceWithStreamingResponse", - "WebhooksResource", - "AsyncWebhooksResource", - "WebhooksResourceWithRawResponse", - "AsyncWebhooksResourceWithRawResponse", - "WebhooksResourceWithStreamingResponse", - "AsyncWebhooksResourceWithStreamingResponse", - "TrackingResource", - "AsyncTrackingResource", - "TrackingResourceWithRawResponse", - "AsyncTrackingResourceWithRawResponse", - "TrackingResourceWithStreamingResponse", - "AsyncTrackingResourceWithStreamingResponse", "LogsResource", "AsyncLogsResource", "LogsResourceWithRawResponse", @@ -108,10 +68,22 @@ "AsyncUsageResourceWithRawResponse", "UsageResourceWithStreamingResponse", "AsyncUsageResourceWithStreamingResponse", + "LimitsResource", + "AsyncLimitsResource", + "LimitsResourceWithRawResponse", + "AsyncLimitsResourceWithRawResponse", + "LimitsResourceWithStreamingResponse", + "AsyncLimitsResourceWithStreamingResponse", "TenantsResource", "AsyncTenantsResource", "TenantsResourceWithRawResponse", "AsyncTenantsResourceWithRawResponse", "TenantsResourceWithStreamingResponse", "AsyncTenantsResourceWithStreamingResponse", + "PlatformResource", + "AsyncPlatformResource", + "PlatformResourceWithRawResponse", + "AsyncPlatformResourceWithRawResponse", + "PlatformResourceWithStreamingResponse", + "AsyncPlatformResourceWithStreamingResponse", ] diff --git a/src/ark/resources/emails.py b/src/ark/resources/emails.py index 4169d3a..77631ce 100644 --- a/src/ark/resources/emails.py +++ b/src/ark/resources/emails.py @@ -59,7 +59,7 @@ def with_streaming_response(self) -> EmailsResourceWithStreamingResponse: def retrieve( self, - id: str, + email_id: str, *, expand: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -96,10 +96,10 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not email_id: + raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}") return self._get( - f"/emails/{id}", + f"/emails/{email_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -137,7 +137,7 @@ def list( **Related endpoints:** - - `GET /emails/{id}` - Get full details of a specific email + - `GET /emails/{emailId}` - Get full details of a specific email - `POST /emails` - Send a new email Args: @@ -200,7 +200,7 @@ def list( def retrieve_deliveries( self, - id: str, + email_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -243,8 +243,8 @@ def retrieve_deliveries( ### Can Retry Manually - Indicates whether you can call `POST /emails/{id}/retry` to manually retry the - email. This is `true` when the raw message content is still available (not + Indicates whether you can call `POST /emails/{emailId}/retry` to manually retry + the email. This is `true` when the raw message content is still available (not expired due to retention policy). Args: @@ -256,10 +256,10 @@ def retrieve_deliveries( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not email_id: + raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}") return self._get( - f"/emails/{id}/deliveries", + f"/emails/{email_id}/deliveries", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -268,7 +268,7 @@ def retrieve_deliveries( def retry( self, - id: str, + email_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -293,10 +293,10 @@ def retry( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not email_id: + raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}") return self._post( - f"/emails/{id}/retry", + f"/emails/{email_id}/retry", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -339,9 +339,9 @@ def send( **Related endpoints:** - - `GET /emails/{id}` - Track delivery status - - `GET /emails/{id}/deliveries` - View delivery attempts - - `POST /emails/{id}/retry` - Retry failed delivery + - `GET /emails/{emailId}` - Track delivery status + - `GET /emails/{emailId}/deliveries` - View delivery attempts + - `POST /emails/{emailId}/retry` - Retry failed delivery Args: from_: Sender email address. Must be from a verified domain OR use sandbox mode. @@ -575,7 +575,7 @@ def with_streaming_response(self) -> AsyncEmailsResourceWithStreamingResponse: async def retrieve( self, - id: str, + email_id: str, *, expand: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -612,10 +612,10 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not email_id: + raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}") return await self._get( - f"/emails/{id}", + f"/emails/{email_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -653,7 +653,7 @@ def list( **Related endpoints:** - - `GET /emails/{id}` - Get full details of a specific email + - `GET /emails/{emailId}` - Get full details of a specific email - `POST /emails` - Send a new email Args: @@ -716,7 +716,7 @@ def list( async def retrieve_deliveries( self, - id: str, + email_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -759,8 +759,8 @@ async def retrieve_deliveries( ### Can Retry Manually - Indicates whether you can call `POST /emails/{id}/retry` to manually retry the - email. This is `true` when the raw message content is still available (not + Indicates whether you can call `POST /emails/{emailId}/retry` to manually retry + the email. This is `true` when the raw message content is still available (not expired due to retention policy). Args: @@ -772,10 +772,10 @@ async def retrieve_deliveries( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not email_id: + raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}") return await self._get( - f"/emails/{id}/deliveries", + f"/emails/{email_id}/deliveries", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -784,7 +784,7 @@ async def retrieve_deliveries( async def retry( self, - id: str, + email_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -809,10 +809,10 @@ async def retry( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + if not email_id: + raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}") return await self._post( - f"/emails/{id}/retry", + f"/emails/{email_id}/retry", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -855,9 +855,9 @@ async def send( **Related endpoints:** - - `GET /emails/{id}` - Track delivery status - - `GET /emails/{id}/deliveries` - View delivery attempts - - `POST /emails/{id}/retry` - Retry failed delivery + - `GET /emails/{emailId}` - Track delivery status + - `GET /emails/{emailId}/deliveries` - View delivery attempts + - `POST /emails/{emailId}/retry` - Retry failed delivery Args: from_: Sender email address. Must be from a verified domain OR use sandbox mode. diff --git a/src/ark/resources/limits.py b/src/ark/resources/limits.py new file mode 100644 index 0000000..8dd9db6 --- /dev/null +++ b/src/ark/resources/limits.py @@ -0,0 +1,171 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.limit_retrieve_response import LimitRetrieveResponse + +__all__ = ["LimitsResource", "AsyncLimitsResource"] + + +class LimitsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> LimitsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return LimitsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> LimitsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return LimitsResourceWithStreamingResponse(self) + + def retrieve( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LimitRetrieveResponse: + """ + Returns current rate limit and send limit information for your account. + + This endpoint is the recommended way to check your account's operational limits. + Use `/usage` endpoints for historical usage analytics. + + **Response includes:** + + - `rateLimit` - API request rate limit (requests per second) + - `sendLimit` - Email sending limit (emails per hour) + - `billing` - Credit balance and auto-recharge configuration + + **Notes:** + + - This request counts against your rate limit + - `sendLimit` may be null if the service is temporarily unavailable + - `billing` is null if billing is not configured + - Send limit resets at the top of each hour + """ + return self._get( + "/limits", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LimitRetrieveResponse, + ) + + +class AsyncLimitsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncLimitsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return AsyncLimitsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncLimitsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return AsyncLimitsResourceWithStreamingResponse(self) + + async def retrieve( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> LimitRetrieveResponse: + """ + Returns current rate limit and send limit information for your account. + + This endpoint is the recommended way to check your account's operational limits. + Use `/usage` endpoints for historical usage analytics. + + **Response includes:** + + - `rateLimit` - API request rate limit (requests per second) + - `sendLimit` - Email sending limit (emails per hour) + - `billing` - Credit balance and auto-recharge configuration + + **Notes:** + + - This request counts against your rate limit + - `sendLimit` may be null if the service is temporarily unavailable + - `billing` is null if billing is not configured + - Send limit resets at the top of each hour + """ + return await self._get( + "/limits", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=LimitRetrieveResponse, + ) + + +class LimitsResourceWithRawResponse: + def __init__(self, limits: LimitsResource) -> None: + self._limits = limits + + self.retrieve = to_raw_response_wrapper( + limits.retrieve, + ) + + +class AsyncLimitsResourceWithRawResponse: + def __init__(self, limits: AsyncLimitsResource) -> None: + self._limits = limits + + self.retrieve = async_to_raw_response_wrapper( + limits.retrieve, + ) + + +class LimitsResourceWithStreamingResponse: + def __init__(self, limits: LimitsResource) -> None: + self._limits = limits + + self.retrieve = to_streamed_response_wrapper( + limits.retrieve, + ) + + +class AsyncLimitsResourceWithStreamingResponse: + def __init__(self, limits: AsyncLimitsResource) -> None: + self._limits = limits + + self.retrieve = async_to_streamed_response_wrapper( + limits.retrieve, + ) diff --git a/src/ark/resources/platform/__init__.py b/src/ark/resources/platform/__init__.py new file mode 100644 index 0000000..17e4578 --- /dev/null +++ b/src/ark/resources/platform/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .platform import ( + PlatformResource, + AsyncPlatformResource, + PlatformResourceWithRawResponse, + AsyncPlatformResourceWithRawResponse, + PlatformResourceWithStreamingResponse, + AsyncPlatformResourceWithStreamingResponse, +) +from .webhooks import ( + WebhooksResource, + AsyncWebhooksResource, + WebhooksResourceWithRawResponse, + AsyncWebhooksResourceWithRawResponse, + WebhooksResourceWithStreamingResponse, + AsyncWebhooksResourceWithStreamingResponse, +) + +__all__ = [ + "WebhooksResource", + "AsyncWebhooksResource", + "WebhooksResourceWithRawResponse", + "AsyncWebhooksResourceWithRawResponse", + "WebhooksResourceWithStreamingResponse", + "AsyncWebhooksResourceWithStreamingResponse", + "PlatformResource", + "AsyncPlatformResource", + "PlatformResourceWithRawResponse", + "AsyncPlatformResourceWithRawResponse", + "PlatformResourceWithStreamingResponse", + "AsyncPlatformResourceWithStreamingResponse", +] diff --git a/src/ark/resources/platform/platform.py b/src/ark/resources/platform/platform.py new file mode 100644 index 0000000..5d6e945 --- /dev/null +++ b/src/ark/resources/platform/platform.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .webhooks import ( + WebhooksResource, + AsyncWebhooksResource, + WebhooksResourceWithRawResponse, + AsyncWebhooksResourceWithRawResponse, + WebhooksResourceWithStreamingResponse, + AsyncWebhooksResourceWithStreamingResponse, +) +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["PlatformResource", "AsyncPlatformResource"] + + +class PlatformResource(SyncAPIResource): + @cached_property + def webhooks(self) -> WebhooksResource: + return WebhooksResource(self._client) + + @cached_property + def with_raw_response(self) -> PlatformResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return PlatformResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PlatformResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return PlatformResourceWithStreamingResponse(self) + + +class AsyncPlatformResource(AsyncAPIResource): + @cached_property + def webhooks(self) -> AsyncWebhooksResource: + return AsyncWebhooksResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncPlatformResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return AsyncPlatformResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPlatformResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return AsyncPlatformResourceWithStreamingResponse(self) + + +class PlatformResourceWithRawResponse: + def __init__(self, platform: PlatformResource) -> None: + self._platform = platform + + @cached_property + def webhooks(self) -> WebhooksResourceWithRawResponse: + return WebhooksResourceWithRawResponse(self._platform.webhooks) + + +class AsyncPlatformResourceWithRawResponse: + def __init__(self, platform: AsyncPlatformResource) -> None: + self._platform = platform + + @cached_property + def webhooks(self) -> AsyncWebhooksResourceWithRawResponse: + return AsyncWebhooksResourceWithRawResponse(self._platform.webhooks) + + +class PlatformResourceWithStreamingResponse: + def __init__(self, platform: PlatformResource) -> None: + self._platform = platform + + @cached_property + def webhooks(self) -> WebhooksResourceWithStreamingResponse: + return WebhooksResourceWithStreamingResponse(self._platform.webhooks) + + +class AsyncPlatformResourceWithStreamingResponse: + def __init__(self, platform: AsyncPlatformResource) -> None: + self._platform = platform + + @cached_property + def webhooks(self) -> AsyncWebhooksResourceWithStreamingResponse: + return AsyncWebhooksResourceWithStreamingResponse(self._platform.webhooks) diff --git a/src/ark/resources/platform/webhooks.py b/src/ark/resources/platform/webhooks.py new file mode 100644 index 0000000..7a30429 --- /dev/null +++ b/src/ark/resources/platform/webhooks.py @@ -0,0 +1,1140 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from ..._base_client import AsyncPaginator, make_request_options +from ...types.platform import ( + webhook_test_params, + webhook_create_params, + webhook_update_params, + webhook_list_deliveries_params, +) +from ...types.platform.webhook_list_response import WebhookListResponse +from ...types.platform.webhook_test_response import WebhookTestResponse +from ...types.platform.webhook_create_response import WebhookCreateResponse +from ...types.platform.webhook_delete_response import WebhookDeleteResponse +from ...types.platform.webhook_update_response import WebhookUpdateResponse +from ...types.platform.webhook_retrieve_response import WebhookRetrieveResponse +from ...types.platform.webhook_list_deliveries_response import WebhookListDeliveriesResponse +from ...types.platform.webhook_replay_delivery_response import WebhookReplayDeliveryResponse +from ...types.platform.webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse + +__all__ = ["WebhooksResource", "AsyncWebhooksResource"] + + +class WebhooksResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> WebhooksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return WebhooksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return WebhooksResourceWithStreamingResponse(self) + + def create( + self, + *, + name: str, + url: str, + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookCreateResponse: + """ + Create a platform webhook to receive email event notifications from all tenants. + + Platform webhooks receive events from **all tenants** in your organization. Each + webhook payload includes a `tenant_id` field to identify which tenant the event + belongs to. + + **Available events:** + + - `MessageSent` - Email accepted by recipient server + - `MessageDeliveryFailed` - Delivery permanently failed + - `MessageDelayed` - Delivery temporarily failed, will retry + - `MessageBounced` - Email bounced + - `MessageHeld` - Email held for review + - `MessageLinkClicked` - Recipient clicked a link + - `MessageLoaded` - Recipient opened the email + - `DomainDNSError` - Domain DNS issue detected + + **Webhook payload includes:** + + - `event` - The event type + - `tenant_id` - The tenant that sent the email + - `timestamp` - Unix timestamp of the event + - `payload` - Event-specific data (message details, status, etc.) + + Args: + name: Display name for the webhook + + url: Webhook endpoint URL (must be HTTPS) + + events: Events to subscribe to. Empty array means all events. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/platform/webhooks", + body=maybe_transform( + { + "name": name, + "url": url, + "events": events, + }, + webhook_create_params.WebhookCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookCreateResponse, + ) + + def retrieve( + self, + webhook_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookRetrieveResponse: + """ + Get detailed information about a specific platform webhook. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._get( + f"/platform/webhooks/{webhook_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookRetrieveResponse, + ) + + def update( + self, + webhook_id: str, + *, + enabled: bool | Omit = omit, + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + | Omit = omit, + name: str | Omit = omit, + url: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookUpdateResponse: + """ + Update a platform webhook's configuration. + + You can update: + + - `name` - Display name for the webhook + - `url` - The endpoint URL (must be HTTPS) + - `events` - Array of event types to receive (empty array = all events) + - `enabled` - Enable or disable the webhook + + Args: + enabled: Enable or disable the webhook + + events: Events to subscribe to. Empty array means all events. + + name: Display name for the webhook + + url: Webhook endpoint URL (must be HTTPS) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._patch( + f"/platform/webhooks/{webhook_id}", + body=maybe_transform( + { + "enabled": enabled, + "events": events, + "name": name, + "url": url, + }, + webhook_update_params.WebhookUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookUpdateResponse, + ) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookListResponse: + """ + Get all platform webhook endpoints configured for your organization. + + Platform webhooks receive events from **all tenants** in your organization, + unlike tenant webhooks which only receive events for a specific tenant. This is + useful for centralized event processing and monitoring. + """ + return self._get( + "/platform/webhooks", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookListResponse, + ) + + def delete( + self, + webhook_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookDeleteResponse: + """Delete a platform webhook. + + This stops all event delivery to the webhook URL. + This action cannot be undone. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._delete( + f"/platform/webhooks/{webhook_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookDeleteResponse, + ) + + def list_deliveries( + self, + *, + after: int | Omit = omit, + before: int | Omit = omit, + event: Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + success: bool | Omit = omit, + tenant_id: str | Omit = omit, + webhook_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPageNumberPagination[WebhookListDeliveriesResponse]: + """ + Get a paginated list of platform webhook delivery attempts. + + Filter by: + + - `webhookId` - Specific webhook + - `tenantId` - Specific tenant + - `event` - Specific event type + - `success` - Successful (2xx) or failed deliveries + - `before`/`after` - Time range (Unix timestamps) + + Deliveries are returned in reverse chronological order. + + Args: + after: Only deliveries after this Unix timestamp + + before: Only deliveries before this Unix timestamp + + event: Filter by event type + + page: Page number (default 1) + + per_page: Items per page (default 30, max 100) + + success: Filter by delivery success + + tenant_id: Filter by tenant ID + + webhook_id: Filter by platform webhook ID + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/platform/webhooks/deliveries", + page=SyncPageNumberPagination[WebhookListDeliveriesResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "event": event, + "page": page, + "per_page": per_page, + "success": success, + "tenant_id": tenant_id, + "webhook_id": webhook_id, + }, + webhook_list_deliveries_params.WebhookListDeliveriesParams, + ), + ), + model=WebhookListDeliveriesResponse, + ) + + def replay_delivery( + self, + delivery_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookReplayDeliveryResponse: + """ + Replay a previous platform webhook delivery. + + This re-sends the original payload with a new timestamp and delivery ID. Useful + for recovering from temporary endpoint failures. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not delivery_id: + raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") + return self._post( + f"/platform/webhooks/deliveries/{delivery_id}/replay", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookReplayDeliveryResponse, + ) + + def retrieve_delivery( + self, + delivery_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookRetrieveDeliveryResponse: + """ + Get detailed information about a specific platform webhook delivery. + + Returns the complete request payload, headers, response, and timing info. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not delivery_id: + raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") + return self._get( + f"/platform/webhooks/deliveries/{delivery_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookRetrieveDeliveryResponse, + ) + + def test( + self, + webhook_id: str, + *, + event: Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookTestResponse: + """ + Send a test payload to your platform webhook endpoint. + + Use this to: + + - Verify your webhook URL is accessible + - Test your payload handling code + - Ensure your server responds correctly + + The test payload is marked with `_test: true` so you can distinguish test events + from real events. + + Args: + event: Event type to simulate + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return self._post( + f"/platform/webhooks/{webhook_id}/test", + body=maybe_transform({"event": event}, webhook_test_params.WebhookTestParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookTestResponse, + ) + + +class AsyncWebhooksResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncWebhooksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return AsyncWebhooksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return AsyncWebhooksResourceWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + url: str, + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookCreateResponse: + """ + Create a platform webhook to receive email event notifications from all tenants. + + Platform webhooks receive events from **all tenants** in your organization. Each + webhook payload includes a `tenant_id` field to identify which tenant the event + belongs to. + + **Available events:** + + - `MessageSent` - Email accepted by recipient server + - `MessageDeliveryFailed` - Delivery permanently failed + - `MessageDelayed` - Delivery temporarily failed, will retry + - `MessageBounced` - Email bounced + - `MessageHeld` - Email held for review + - `MessageLinkClicked` - Recipient clicked a link + - `MessageLoaded` - Recipient opened the email + - `DomainDNSError` - Domain DNS issue detected + + **Webhook payload includes:** + + - `event` - The event type + - `tenant_id` - The tenant that sent the email + - `timestamp` - Unix timestamp of the event + - `payload` - Event-specific data (message details, status, etc.) + + Args: + name: Display name for the webhook + + url: Webhook endpoint URL (must be HTTPS) + + events: Events to subscribe to. Empty array means all events. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/platform/webhooks", + body=await async_maybe_transform( + { + "name": name, + "url": url, + "events": events, + }, + webhook_create_params.WebhookCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookCreateResponse, + ) + + async def retrieve( + self, + webhook_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookRetrieveResponse: + """ + Get detailed information about a specific platform webhook. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._get( + f"/platform/webhooks/{webhook_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookRetrieveResponse, + ) + + async def update( + self, + webhook_id: str, + *, + enabled: bool | Omit = omit, + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + | Omit = omit, + name: str | Omit = omit, + url: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookUpdateResponse: + """ + Update a platform webhook's configuration. + + You can update: + + - `name` - Display name for the webhook + - `url` - The endpoint URL (must be HTTPS) + - `events` - Array of event types to receive (empty array = all events) + - `enabled` - Enable or disable the webhook + + Args: + enabled: Enable or disable the webhook + + events: Events to subscribe to. Empty array means all events. + + name: Display name for the webhook + + url: Webhook endpoint URL (must be HTTPS) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._patch( + f"/platform/webhooks/{webhook_id}", + body=await async_maybe_transform( + { + "enabled": enabled, + "events": events, + "name": name, + "url": url, + }, + webhook_update_params.WebhookUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookUpdateResponse, + ) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookListResponse: + """ + Get all platform webhook endpoints configured for your organization. + + Platform webhooks receive events from **all tenants** in your organization, + unlike tenant webhooks which only receive events for a specific tenant. This is + useful for centralized event processing and monitoring. + """ + return await self._get( + "/platform/webhooks", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookListResponse, + ) + + async def delete( + self, + webhook_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookDeleteResponse: + """Delete a platform webhook. + + This stops all event delivery to the webhook URL. + This action cannot be undone. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._delete( + f"/platform/webhooks/{webhook_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookDeleteResponse, + ) + + def list_deliveries( + self, + *, + after: int | Omit = omit, + before: int | Omit = omit, + event: Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + | Omit = omit, + page: int | Omit = omit, + per_page: int | Omit = omit, + success: bool | Omit = omit, + tenant_id: str | Omit = omit, + webhook_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[WebhookListDeliveriesResponse, AsyncPageNumberPagination[WebhookListDeliveriesResponse]]: + """ + Get a paginated list of platform webhook delivery attempts. + + Filter by: + + - `webhookId` - Specific webhook + - `tenantId` - Specific tenant + - `event` - Specific event type + - `success` - Successful (2xx) or failed deliveries + - `before`/`after` - Time range (Unix timestamps) + + Deliveries are returned in reverse chronological order. + + Args: + after: Only deliveries after this Unix timestamp + + before: Only deliveries before this Unix timestamp + + event: Filter by event type + + page: Page number (default 1) + + per_page: Items per page (default 30, max 100) + + success: Filter by delivery success + + tenant_id: Filter by tenant ID + + webhook_id: Filter by platform webhook ID + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/platform/webhooks/deliveries", + page=AsyncPageNumberPagination[WebhookListDeliveriesResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "event": event, + "page": page, + "per_page": per_page, + "success": success, + "tenant_id": tenant_id, + "webhook_id": webhook_id, + }, + webhook_list_deliveries_params.WebhookListDeliveriesParams, + ), + ), + model=WebhookListDeliveriesResponse, + ) + + async def replay_delivery( + self, + delivery_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookReplayDeliveryResponse: + """ + Replay a previous platform webhook delivery. + + This re-sends the original payload with a new timestamp and delivery ID. Useful + for recovering from temporary endpoint failures. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not delivery_id: + raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") + return await self._post( + f"/platform/webhooks/deliveries/{delivery_id}/replay", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookReplayDeliveryResponse, + ) + + async def retrieve_delivery( + self, + delivery_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookRetrieveDeliveryResponse: + """ + Get detailed information about a specific platform webhook delivery. + + Returns the complete request payload, headers, response, and timing info. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not delivery_id: + raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") + return await self._get( + f"/platform/webhooks/deliveries/{delivery_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookRetrieveDeliveryResponse, + ) + + async def test( + self, + webhook_id: str, + *, + event: Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> WebhookTestResponse: + """ + Send a test payload to your platform webhook endpoint. + + Use this to: + + - Verify your webhook URL is accessible + - Test your payload handling code + - Ensure your server responds correctly + + The test payload is marked with `_test: true` so you can distinguish test events + from real events. + + Args: + event: Event type to simulate + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not webhook_id: + raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") + return await self._post( + f"/platform/webhooks/{webhook_id}/test", + body=await async_maybe_transform({"event": event}, webhook_test_params.WebhookTestParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=WebhookTestResponse, + ) + + +class WebhooksResourceWithRawResponse: + def __init__(self, webhooks: WebhooksResource) -> None: + self._webhooks = webhooks + + self.create = to_raw_response_wrapper( + webhooks.create, + ) + self.retrieve = to_raw_response_wrapper( + webhooks.retrieve, + ) + self.update = to_raw_response_wrapper( + webhooks.update, + ) + self.list = to_raw_response_wrapper( + webhooks.list, + ) + self.delete = to_raw_response_wrapper( + webhooks.delete, + ) + self.list_deliveries = to_raw_response_wrapper( + webhooks.list_deliveries, + ) + self.replay_delivery = to_raw_response_wrapper( + webhooks.replay_delivery, + ) + self.retrieve_delivery = to_raw_response_wrapper( + webhooks.retrieve_delivery, + ) + self.test = to_raw_response_wrapper( + webhooks.test, + ) + + +class AsyncWebhooksResourceWithRawResponse: + def __init__(self, webhooks: AsyncWebhooksResource) -> None: + self._webhooks = webhooks + + self.create = async_to_raw_response_wrapper( + webhooks.create, + ) + self.retrieve = async_to_raw_response_wrapper( + webhooks.retrieve, + ) + self.update = async_to_raw_response_wrapper( + webhooks.update, + ) + self.list = async_to_raw_response_wrapper( + webhooks.list, + ) + self.delete = async_to_raw_response_wrapper( + webhooks.delete, + ) + self.list_deliveries = async_to_raw_response_wrapper( + webhooks.list_deliveries, + ) + self.replay_delivery = async_to_raw_response_wrapper( + webhooks.replay_delivery, + ) + self.retrieve_delivery = async_to_raw_response_wrapper( + webhooks.retrieve_delivery, + ) + self.test = async_to_raw_response_wrapper( + webhooks.test, + ) + + +class WebhooksResourceWithStreamingResponse: + def __init__(self, webhooks: WebhooksResource) -> None: + self._webhooks = webhooks + + self.create = to_streamed_response_wrapper( + webhooks.create, + ) + self.retrieve = to_streamed_response_wrapper( + webhooks.retrieve, + ) + self.update = to_streamed_response_wrapper( + webhooks.update, + ) + self.list = to_streamed_response_wrapper( + webhooks.list, + ) + self.delete = to_streamed_response_wrapper( + webhooks.delete, + ) + self.list_deliveries = to_streamed_response_wrapper( + webhooks.list_deliveries, + ) + self.replay_delivery = to_streamed_response_wrapper( + webhooks.replay_delivery, + ) + self.retrieve_delivery = to_streamed_response_wrapper( + webhooks.retrieve_delivery, + ) + self.test = to_streamed_response_wrapper( + webhooks.test, + ) + + +class AsyncWebhooksResourceWithStreamingResponse: + def __init__(self, webhooks: AsyncWebhooksResource) -> None: + self._webhooks = webhooks + + self.create = async_to_streamed_response_wrapper( + webhooks.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + webhooks.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + webhooks.update, + ) + self.list = async_to_streamed_response_wrapper( + webhooks.list, + ) + self.delete = async_to_streamed_response_wrapper( + webhooks.delete, + ) + self.list_deliveries = async_to_streamed_response_wrapper( + webhooks.list_deliveries, + ) + self.replay_delivery = async_to_streamed_response_wrapper( + webhooks.replay_delivery, + ) + self.retrieve_delivery = async_to_streamed_response_wrapper( + webhooks.retrieve_delivery, + ) + self.test = async_to_streamed_response_wrapper( + webhooks.test, + ) diff --git a/src/ark/resources/tenants/__init__.py b/src/ark/resources/tenants/__init__.py new file mode 100644 index 0000000..31a4e7e --- /dev/null +++ b/src/ark/resources/tenants/__init__.py @@ -0,0 +1,103 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .usage import ( + UsageResource, + AsyncUsageResource, + UsageResourceWithRawResponse, + AsyncUsageResourceWithRawResponse, + UsageResourceWithStreamingResponse, + AsyncUsageResourceWithStreamingResponse, +) +from .domains import ( + DomainsResource, + AsyncDomainsResource, + DomainsResourceWithRawResponse, + AsyncDomainsResourceWithRawResponse, + DomainsResourceWithStreamingResponse, + AsyncDomainsResourceWithStreamingResponse, +) +from .tenants import ( + TenantsResource, + AsyncTenantsResource, + TenantsResourceWithRawResponse, + AsyncTenantsResourceWithRawResponse, + TenantsResourceWithStreamingResponse, + AsyncTenantsResourceWithStreamingResponse, +) +from .tracking import ( + TrackingResource, + AsyncTrackingResource, + TrackingResourceWithRawResponse, + AsyncTrackingResourceWithRawResponse, + TrackingResourceWithStreamingResponse, + AsyncTrackingResourceWithStreamingResponse, +) +from .webhooks import ( + WebhooksResource, + AsyncWebhooksResource, + WebhooksResourceWithRawResponse, + AsyncWebhooksResourceWithRawResponse, + WebhooksResourceWithStreamingResponse, + AsyncWebhooksResourceWithStreamingResponse, +) +from .credentials import ( + CredentialsResource, + AsyncCredentialsResource, + CredentialsResourceWithRawResponse, + AsyncCredentialsResourceWithRawResponse, + CredentialsResourceWithStreamingResponse, + AsyncCredentialsResourceWithStreamingResponse, +) +from .suppressions import ( + SuppressionsResource, + AsyncSuppressionsResource, + SuppressionsResourceWithRawResponse, + AsyncSuppressionsResourceWithRawResponse, + SuppressionsResourceWithStreamingResponse, + AsyncSuppressionsResourceWithStreamingResponse, +) + +__all__ = [ + "CredentialsResource", + "AsyncCredentialsResource", + "CredentialsResourceWithRawResponse", + "AsyncCredentialsResourceWithRawResponse", + "CredentialsResourceWithStreamingResponse", + "AsyncCredentialsResourceWithStreamingResponse", + "DomainsResource", + "AsyncDomainsResource", + "DomainsResourceWithRawResponse", + "AsyncDomainsResourceWithRawResponse", + "DomainsResourceWithStreamingResponse", + "AsyncDomainsResourceWithStreamingResponse", + "SuppressionsResource", + "AsyncSuppressionsResource", + "SuppressionsResourceWithRawResponse", + "AsyncSuppressionsResourceWithRawResponse", + "SuppressionsResourceWithStreamingResponse", + "AsyncSuppressionsResourceWithStreamingResponse", + "WebhooksResource", + "AsyncWebhooksResource", + "WebhooksResourceWithRawResponse", + "AsyncWebhooksResourceWithRawResponse", + "WebhooksResourceWithStreamingResponse", + "AsyncWebhooksResourceWithStreamingResponse", + "TrackingResource", + "AsyncTrackingResource", + "TrackingResourceWithRawResponse", + "AsyncTrackingResourceWithRawResponse", + "TrackingResourceWithStreamingResponse", + "AsyncTrackingResourceWithStreamingResponse", + "UsageResource", + "AsyncUsageResource", + "UsageResourceWithRawResponse", + "AsyncUsageResourceWithRawResponse", + "UsageResourceWithStreamingResponse", + "AsyncUsageResourceWithStreamingResponse", + "TenantsResource", + "AsyncTenantsResource", + "TenantsResourceWithRawResponse", + "AsyncTenantsResourceWithRawResponse", + "TenantsResourceWithStreamingResponse", + "AsyncTenantsResourceWithStreamingResponse", +] diff --git a/src/ark/resources/tenants/credentials.py b/src/ark/resources/tenants/credentials.py new file mode 100644 index 0000000..2affa6a --- /dev/null +++ b/src/ark/resources/tenants/credentials.py @@ -0,0 +1,679 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from ..._base_client import AsyncPaginator, make_request_options +from ...types.tenants import ( + credential_list_params, + credential_create_params, + credential_update_params, + credential_retrieve_params, +) +from ...types.tenants.credential_list_response import CredentialListResponse +from ...types.tenants.credential_create_response import CredentialCreateResponse +from ...types.tenants.credential_delete_response import CredentialDeleteResponse +from ...types.tenants.credential_update_response import CredentialUpdateResponse +from ...types.tenants.credential_retrieve_response import CredentialRetrieveResponse + +__all__ = ["CredentialsResource", "AsyncCredentialsResource"] + + +class CredentialsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> CredentialsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return CredentialsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> CredentialsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return CredentialsResourceWithStreamingResponse(self) + + def create( + self, + tenant_id: str, + *, + name: str, + type: Literal["smtp", "api"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialCreateResponse: + """Create a new SMTP or API credential for a tenant. + + The credential can be used to + send emails via Ark on behalf of the tenant. + + **Important:** The credential key is only returned once at creation time. Store + it securely - you cannot retrieve it again. + + **Credential Types:** + + - `smtp` - For SMTP-based email sending. Returns both `key` and `smtpUsername`. + - `api` - For API-based email sending. Returns only `key`. + + Args: + name: Name for the credential. Can only contain letters, numbers, hyphens, and + underscores. Max 50 characters. + + type: + Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._post( + f"/tenants/{tenant_id}/credentials", + body=maybe_transform( + { + "name": name, + "type": type, + }, + credential_create_params.CredentialCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialCreateResponse, + ) + + def retrieve( + self, + credential_id: int, + *, + tenant_id: str, + reveal: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialRetrieveResponse: + """ + Get details of a specific credential. + + **Revealing the key:** By default, the credential key is not returned. Pass + `reveal=true` to include the key in the response. Use this sparingly and only + when you need to retrieve the key (e.g., for configuration). + + Args: + reveal: Set to `true` to include the credential key in the response + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._get( + f"/tenants/{tenant_id}/credentials/{credential_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"reveal": reveal}, credential_retrieve_params.CredentialRetrieveParams), + ), + cast_to=CredentialRetrieveResponse, + ) + + def update( + self, + credential_id: int, + *, + tenant_id: str, + hold: bool | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialUpdateResponse: + """ + Update a credential's name or hold status. + + **Hold Status:** + + - When `hold: true`, the credential is disabled and cannot be used to send + emails. + - When `hold: false`, the credential is active and can send emails. + - Use this to temporarily disable a credential without deleting it. + + Args: + hold: Set to `true` to disable the credential (put on hold). Set to `false` to enable + the credential (release from hold). + + name: New name for the credential + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._patch( + f"/tenants/{tenant_id}/credentials/{credential_id}", + body=maybe_transform( + { + "hold": hold, + "name": name, + }, + credential_update_params.CredentialUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialUpdateResponse, + ) + + def list( + self, + tenant_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + type: Literal["smtp", "api"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPageNumberPagination[CredentialListResponse]: + """List all SMTP and API credentials for a tenant. + + Credentials are used to send + emails via Ark on behalf of the tenant. + + **Security:** Credential keys are not returned in the list response. Use the + retrieve endpoint with `reveal=true` to get the key. + + Args: + page: Page number (1-indexed) + + per_page: Number of items per page (max 100) + + type: Filter by credential type + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._get_api_list( + f"/tenants/{tenant_id}/credentials", + page=SyncPageNumberPagination[CredentialListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + "type": type, + }, + credential_list_params.CredentialListParams, + ), + ), + model=CredentialListResponse, + ) + + def delete( + self, + credential_id: int, + *, + tenant_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialDeleteResponse: + """Permanently delete (revoke) a credential. + + The credential can no longer be used + to send emails. + + **Warning:** This action is irreversible. If you want to temporarily disable a + credential, use the update endpoint to set `hold: true` instead. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._delete( + f"/tenants/{tenant_id}/credentials/{credential_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialDeleteResponse, + ) + + +class AsyncCredentialsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncCredentialsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return AsyncCredentialsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncCredentialsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return AsyncCredentialsResourceWithStreamingResponse(self) + + async def create( + self, + tenant_id: str, + *, + name: str, + type: Literal["smtp", "api"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialCreateResponse: + """Create a new SMTP or API credential for a tenant. + + The credential can be used to + send emails via Ark on behalf of the tenant. + + **Important:** The credential key is only returned once at creation time. Store + it securely - you cannot retrieve it again. + + **Credential Types:** + + - `smtp` - For SMTP-based email sending. Returns both `key` and `smtpUsername`. + - `api` - For API-based email sending. Returns only `key`. + + Args: + name: Name for the credential. Can only contain letters, numbers, hyphens, and + underscores. Max 50 characters. + + type: + Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return await self._post( + f"/tenants/{tenant_id}/credentials", + body=await async_maybe_transform( + { + "name": name, + "type": type, + }, + credential_create_params.CredentialCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialCreateResponse, + ) + + async def retrieve( + self, + credential_id: int, + *, + tenant_id: str, + reveal: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialRetrieveResponse: + """ + Get details of a specific credential. + + **Revealing the key:** By default, the credential key is not returned. Pass + `reveal=true` to include the key in the response. Use this sparingly and only + when you need to retrieve the key (e.g., for configuration). + + Args: + reveal: Set to `true` to include the credential key in the response + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return await self._get( + f"/tenants/{tenant_id}/credentials/{credential_id}", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"reveal": reveal}, credential_retrieve_params.CredentialRetrieveParams + ), + ), + cast_to=CredentialRetrieveResponse, + ) + + async def update( + self, + credential_id: int, + *, + tenant_id: str, + hold: bool | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialUpdateResponse: + """ + Update a credential's name or hold status. + + **Hold Status:** + + - When `hold: true`, the credential is disabled and cannot be used to send + emails. + - When `hold: false`, the credential is active and can send emails. + - Use this to temporarily disable a credential without deleting it. + + Args: + hold: Set to `true` to disable the credential (put on hold). Set to `false` to enable + the credential (release from hold). + + name: New name for the credential + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return await self._patch( + f"/tenants/{tenant_id}/credentials/{credential_id}", + body=await async_maybe_transform( + { + "hold": hold, + "name": name, + }, + credential_update_params.CredentialUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialUpdateResponse, + ) + + def list( + self, + tenant_id: str, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + type: Literal["smtp", "api"] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[CredentialListResponse, AsyncPageNumberPagination[CredentialListResponse]]: + """List all SMTP and API credentials for a tenant. + + Credentials are used to send + emails via Ark on behalf of the tenant. + + **Security:** Credential keys are not returned in the list response. Use the + retrieve endpoint with `reveal=true` to get the key. + + Args: + page: Page number (1-indexed) + + per_page: Number of items per page (max 100) + + type: Filter by credential type + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._get_api_list( + f"/tenants/{tenant_id}/credentials", + page=AsyncPageNumberPagination[CredentialListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "page": page, + "per_page": per_page, + "type": type, + }, + credential_list_params.CredentialListParams, + ), + ), + model=CredentialListResponse, + ) + + async def delete( + self, + credential_id: int, + *, + tenant_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> CredentialDeleteResponse: + """Permanently delete (revoke) a credential. + + The credential can no longer be used + to send emails. + + **Warning:** This action is irreversible. If you want to temporarily disable a + credential, use the update endpoint to set `hold: true` instead. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return await self._delete( + f"/tenants/{tenant_id}/credentials/{credential_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CredentialDeleteResponse, + ) + + +class CredentialsResourceWithRawResponse: + def __init__(self, credentials: CredentialsResource) -> None: + self._credentials = credentials + + self.create = to_raw_response_wrapper( + credentials.create, + ) + self.retrieve = to_raw_response_wrapper( + credentials.retrieve, + ) + self.update = to_raw_response_wrapper( + credentials.update, + ) + self.list = to_raw_response_wrapper( + credentials.list, + ) + self.delete = to_raw_response_wrapper( + credentials.delete, + ) + + +class AsyncCredentialsResourceWithRawResponse: + def __init__(self, credentials: AsyncCredentialsResource) -> None: + self._credentials = credentials + + self.create = async_to_raw_response_wrapper( + credentials.create, + ) + self.retrieve = async_to_raw_response_wrapper( + credentials.retrieve, + ) + self.update = async_to_raw_response_wrapper( + credentials.update, + ) + self.list = async_to_raw_response_wrapper( + credentials.list, + ) + self.delete = async_to_raw_response_wrapper( + credentials.delete, + ) + + +class CredentialsResourceWithStreamingResponse: + def __init__(self, credentials: CredentialsResource) -> None: + self._credentials = credentials + + self.create = to_streamed_response_wrapper( + credentials.create, + ) + self.retrieve = to_streamed_response_wrapper( + credentials.retrieve, + ) + self.update = to_streamed_response_wrapper( + credentials.update, + ) + self.list = to_streamed_response_wrapper( + credentials.list, + ) + self.delete = to_streamed_response_wrapper( + credentials.delete, + ) + + +class AsyncCredentialsResourceWithStreamingResponse: + def __init__(self, credentials: AsyncCredentialsResource) -> None: + self._credentials = credentials + + self.create = async_to_streamed_response_wrapper( + credentials.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + credentials.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + credentials.update, + ) + self.list = async_to_streamed_response_wrapper( + credentials.list, + ) + self.delete = async_to_streamed_response_wrapper( + credentials.delete, + ) diff --git a/src/ark/resources/domains.py b/src/ark/resources/tenants/domains.py similarity index 78% rename from src/ark/resources/domains.py rename to src/ark/resources/tenants/domains.py index ff0fadc..e560458 100644 --- a/src/ark/resources/domains.py +++ b/src/ark/resources/tenants/domains.py @@ -4,23 +4,23 @@ import httpx -from ..types import domain_create_params -from .._types import Body, Query, Headers, NotGiven, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options -from ..types.domain_list_response import DomainListResponse -from ..types.domain_create_response import DomainCreateResponse -from ..types.domain_delete_response import DomainDeleteResponse -from ..types.domain_verify_response import DomainVerifyResponse -from ..types.domain_retrieve_response import DomainRetrieveResponse +from ..._base_client import make_request_options +from ...types.tenants import domain_create_params +from ...types.tenants.domain_list_response import DomainListResponse +from ...types.tenants.domain_create_response import DomainCreateResponse +from ...types.tenants.domain_delete_response import DomainDeleteResponse +from ...types.tenants.domain_verify_response import DomainVerifyResponse +from ...types.tenants.domain_retrieve_response import DomainRetrieveResponse __all__ = ["DomainsResource", "AsyncDomainsResource"] @@ -47,6 +47,7 @@ def with_streaming_response(self) -> DomainsResourceWithStreamingResponse: def create( self, + tenant_id: str, *, name: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -56,10 +57,12 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainCreateResponse: - """Add a new domain for sending emails. + """Add a new sending domain to a tenant. - Returns DNS records that must be configured - before the domain can be verified. + Returns DNS records that must be + configured before the domain can be verified. + + Each tenant gets their own isolated mail server for domain isolation. **Required DNS records:** @@ -67,7 +70,8 @@ def create( - **DKIM** - TXT record for email signing - **Return Path** - CNAME for bounce handling - After adding DNS records, call `POST /domains/{id}/verify` to verify. + After adding DNS records, call + `POST /tenants/{tenantId}/domains/{domainId}/verify` to verify. Args: name: Domain name (e.g., "mail.example.com") @@ -80,8 +84,10 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._post( - "/domains", + f"/tenants/{tenant_id}/domains", body=maybe_transform({"name": name}, domain_create_params.DomainCreateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -93,6 +99,7 @@ def retrieve( self, domain_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -101,7 +108,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainRetrieveResponse: """ - Get detailed information about a domain including DNS record status + Get detailed information about a domain including DNS record status. Args: extra_headers: Send extra headers @@ -112,10 +119,12 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not domain_id: raise ValueError(f"Expected a non-empty value for `domain_id` but received {domain_id!r}") return self._get( - f"/domains/{domain_id}", + f"/tenants/{tenant_id}/domains/{domain_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -124,6 +133,7 @@ def retrieve( def list( self, + tenant_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -132,9 +142,22 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainListResponse: - """Get all sending domains with their verification status""" + """ + Get all sending domains for a specific tenant with their verification status. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._get( - "/domains", + f"/tenants/{tenant_id}/domains", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -145,6 +168,7 @@ def delete( self, domain_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -152,10 +176,10 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainDeleteResponse: - """Remove a sending domain. + """Remove a sending domain from a tenant. - You will no longer be able to send emails from this - domain. + You will no longer be able to send emails + from this domain. **Warning:** This action cannot be undone. @@ -168,10 +192,12 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not domain_id: raise ValueError(f"Expected a non-empty value for `domain_id` but received {domain_id!r}") return self._delete( - f"/domains/{domain_id}", + f"/tenants/{tenant_id}/domains/{domain_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -182,6 +208,7 @@ def verify( self, domain_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -205,10 +232,12 @@ def verify( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not domain_id: raise ValueError(f"Expected a non-empty value for `domain_id` but received {domain_id!r}") return self._post( - f"/domains/{domain_id}/verify", + f"/tenants/{tenant_id}/domains/{domain_id}/verify", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -238,6 +267,7 @@ def with_streaming_response(self) -> AsyncDomainsResourceWithStreamingResponse: async def create( self, + tenant_id: str, *, name: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -247,10 +277,12 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainCreateResponse: - """Add a new domain for sending emails. + """Add a new sending domain to a tenant. - Returns DNS records that must be configured - before the domain can be verified. + Returns DNS records that must be + configured before the domain can be verified. + + Each tenant gets their own isolated mail server for domain isolation. **Required DNS records:** @@ -258,7 +290,8 @@ async def create( - **DKIM** - TXT record for email signing - **Return Path** - CNAME for bounce handling - After adding DNS records, call `POST /domains/{id}/verify` to verify. + After adding DNS records, call + `POST /tenants/{tenantId}/domains/{domainId}/verify` to verify. Args: name: Domain name (e.g., "mail.example.com") @@ -271,8 +304,10 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._post( - "/domains", + f"/tenants/{tenant_id}/domains", body=await async_maybe_transform({"name": name}, domain_create_params.DomainCreateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -284,6 +319,7 @@ async def retrieve( self, domain_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -292,7 +328,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainRetrieveResponse: """ - Get detailed information about a domain including DNS record status + Get detailed information about a domain including DNS record status. Args: extra_headers: Send extra headers @@ -303,10 +339,12 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not domain_id: raise ValueError(f"Expected a non-empty value for `domain_id` but received {domain_id!r}") return await self._get( - f"/domains/{domain_id}", + f"/tenants/{tenant_id}/domains/{domain_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -315,6 +353,7 @@ async def retrieve( async def list( self, + tenant_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -323,9 +362,22 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainListResponse: - """Get all sending domains with their verification status""" + """ + Get all sending domains for a specific tenant with their verification status. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._get( - "/domains", + f"/tenants/{tenant_id}/domains", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -336,6 +388,7 @@ async def delete( self, domain_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -343,10 +396,10 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DomainDeleteResponse: - """Remove a sending domain. + """Remove a sending domain from a tenant. - You will no longer be able to send emails from this - domain. + You will no longer be able to send emails + from this domain. **Warning:** This action cannot be undone. @@ -359,10 +412,12 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not domain_id: raise ValueError(f"Expected a non-empty value for `domain_id` but received {domain_id!r}") return await self._delete( - f"/domains/{domain_id}", + f"/tenants/{tenant_id}/domains/{domain_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -373,6 +428,7 @@ async def verify( self, domain_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -396,10 +452,12 @@ async def verify( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not domain_id: raise ValueError(f"Expected a non-empty value for `domain_id` but received {domain_id!r}") return await self._post( - f"/domains/{domain_id}/verify", + f"/tenants/{tenant_id}/domains/{domain_id}/verify", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/ark/resources/suppressions.py b/src/ark/resources/tenants/suppressions.py similarity index 73% rename from src/ark/resources/suppressions.py rename to src/ark/resources/tenants/suppressions.py index 9cfca89..070b7e1 100644 --- a/src/ark/resources/suppressions.py +++ b/src/ark/resources/tenants/suppressions.py @@ -2,28 +2,27 @@ from __future__ import annotations -from typing import Iterable, Optional +from typing import Optional import httpx -from ..types import suppression_list_params, suppression_create_params, suppression_bulk_create_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..pagination import SyncPageNumberPagination, AsyncPageNumberPagination -from .._base_client import AsyncPaginator, make_request_options -from ..types.suppression_list_response import SuppressionListResponse -from ..types.suppression_create_response import SuppressionCreateResponse -from ..types.suppression_delete_response import SuppressionDeleteResponse -from ..types.suppression_retrieve_response import SuppressionRetrieveResponse -from ..types.suppression_bulk_create_response import SuppressionBulkCreateResponse +from ...pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from ..._base_client import AsyncPaginator, make_request_options +from ...types.tenants import suppression_list_params, suppression_create_params +from ...types.tenants.suppression_list_response import SuppressionListResponse +from ...types.tenants.suppression_create_response import SuppressionCreateResponse +from ...types.tenants.suppression_delete_response import SuppressionDeleteResponse +from ...types.tenants.suppression_retrieve_response import SuppressionRetrieveResponse __all__ = ["SuppressionsResource", "AsyncSuppressionsResource"] @@ -50,6 +49,7 @@ def with_streaming_response(self) -> SuppressionsResourceWithStreamingResponse: def create( self, + tenant_id: str, *, address: str, reason: Optional[str] | Omit = omit, @@ -60,10 +60,10 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SuppressionCreateResponse: - """Add an email address to the suppression list. + """Add an email address to the tenant's suppression list. - The address will not receive any - emails until removed. + The address will not + receive any emails from this tenant until removed. Args: address: Email address to suppress @@ -78,8 +78,10 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._post( - "/suppressions", + f"/tenants/{tenant_id}/suppressions", body=maybe_transform( { "address": address, @@ -97,6 +99,7 @@ def retrieve( self, email: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -105,7 +108,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SuppressionRetrieveResponse: """ - Check if a specific email address is on the suppression list + Check if a specific email address is on the tenant's suppression list. Args: extra_headers: Send extra headers @@ -116,10 +119,12 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not email: raise ValueError(f"Expected a non-empty value for `email` but received {email!r}") return self._get( - f"/suppressions/{email}", + f"/tenants/{tenant_id}/suppressions/{email}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -128,6 +133,7 @@ def retrieve( def list( self, + tenant_id: str, *, page: int | Omit = omit, per_page: int | Omit = omit, @@ -138,10 +144,10 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncPageNumberPagination[SuppressionListResponse]: - """Get all email addresses on the suppression list. + """Get all email addresses on the tenant's suppression list. - These addresses will not - receive any emails. + These addresses will + not receive any emails from this tenant. Args: extra_headers: Send extra headers @@ -152,8 +158,10 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._get_api_list( - "/suppressions", + f"/tenants/{tenant_id}/suppressions", page=SyncPageNumberPagination[SuppressionListResponse], options=make_request_options( extra_headers=extra_headers, @@ -175,6 +183,7 @@ def delete( self, email: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -182,10 +191,10 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SuppressionDeleteResponse: - """Remove an email address from the suppression list. + """Remove an email address from the tenant's suppression list. - The address will be able to - receive emails again. + The address will be + able to receive emails from this tenant again. Args: extra_headers: Send extra headers @@ -196,50 +205,18 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not email: raise ValueError(f"Expected a non-empty value for `email` but received {email!r}") return self._delete( - f"/suppressions/{email}", + f"/tenants/{tenant_id}/suppressions/{email}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=SuppressionDeleteResponse, ) - def bulk_create( - self, - *, - suppressions: Iterable[suppression_bulk_create_params.Suppression], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SuppressionBulkCreateResponse: - """ - Add up to 1000 email addresses to the suppression list at once - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/suppressions/bulk", - body=maybe_transform( - {"suppressions": suppressions}, suppression_bulk_create_params.SuppressionBulkCreateParams - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SuppressionBulkCreateResponse, - ) - class AsyncSuppressionsResource(AsyncAPIResource): @cached_property @@ -263,6 +240,7 @@ def with_streaming_response(self) -> AsyncSuppressionsResourceWithStreamingRespo async def create( self, + tenant_id: str, *, address: str, reason: Optional[str] | Omit = omit, @@ -273,10 +251,10 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SuppressionCreateResponse: - """Add an email address to the suppression list. + """Add an email address to the tenant's suppression list. - The address will not receive any - emails until removed. + The address will not + receive any emails from this tenant until removed. Args: address: Email address to suppress @@ -291,8 +269,10 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._post( - "/suppressions", + f"/tenants/{tenant_id}/suppressions", body=await async_maybe_transform( { "address": address, @@ -310,6 +290,7 @@ async def retrieve( self, email: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -318,7 +299,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SuppressionRetrieveResponse: """ - Check if a specific email address is on the suppression list + Check if a specific email address is on the tenant's suppression list. Args: extra_headers: Send extra headers @@ -329,10 +310,12 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not email: raise ValueError(f"Expected a non-empty value for `email` but received {email!r}") return await self._get( - f"/suppressions/{email}", + f"/tenants/{tenant_id}/suppressions/{email}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -341,6 +324,7 @@ async def retrieve( def list( self, + tenant_id: str, *, page: int | Omit = omit, per_page: int | Omit = omit, @@ -351,10 +335,10 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[SuppressionListResponse, AsyncPageNumberPagination[SuppressionListResponse]]: - """Get all email addresses on the suppression list. + """Get all email addresses on the tenant's suppression list. - These addresses will not - receive any emails. + These addresses will + not receive any emails from this tenant. Args: extra_headers: Send extra headers @@ -365,8 +349,10 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._get_api_list( - "/suppressions", + f"/tenants/{tenant_id}/suppressions", page=AsyncPageNumberPagination[SuppressionListResponse], options=make_request_options( extra_headers=extra_headers, @@ -388,6 +374,7 @@ async def delete( self, email: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -395,10 +382,10 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SuppressionDeleteResponse: - """Remove an email address from the suppression list. + """Remove an email address from the tenant's suppression list. - The address will be able to - receive emails again. + The address will be + able to receive emails from this tenant again. Args: extra_headers: Send extra headers @@ -409,50 +396,18 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not email: raise ValueError(f"Expected a non-empty value for `email` but received {email!r}") return await self._delete( - f"/suppressions/{email}", + f"/tenants/{tenant_id}/suppressions/{email}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=SuppressionDeleteResponse, ) - async def bulk_create( - self, - *, - suppressions: Iterable[suppression_bulk_create_params.Suppression], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SuppressionBulkCreateResponse: - """ - Add up to 1000 email addresses to the suppression list at once - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/suppressions/bulk", - body=await async_maybe_transform( - {"suppressions": suppressions}, suppression_bulk_create_params.SuppressionBulkCreateParams - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SuppressionBulkCreateResponse, - ) - class SuppressionsResourceWithRawResponse: def __init__(self, suppressions: SuppressionsResource) -> None: @@ -470,9 +425,6 @@ def __init__(self, suppressions: SuppressionsResource) -> None: self.delete = to_raw_response_wrapper( suppressions.delete, ) - self.bulk_create = to_raw_response_wrapper( - suppressions.bulk_create, - ) class AsyncSuppressionsResourceWithRawResponse: @@ -491,9 +443,6 @@ def __init__(self, suppressions: AsyncSuppressionsResource) -> None: self.delete = async_to_raw_response_wrapper( suppressions.delete, ) - self.bulk_create = async_to_raw_response_wrapper( - suppressions.bulk_create, - ) class SuppressionsResourceWithStreamingResponse: @@ -512,9 +461,6 @@ def __init__(self, suppressions: SuppressionsResource) -> None: self.delete = to_streamed_response_wrapper( suppressions.delete, ) - self.bulk_create = to_streamed_response_wrapper( - suppressions.bulk_create, - ) class AsyncSuppressionsResourceWithStreamingResponse: @@ -533,6 +479,3 @@ def __init__(self, suppressions: AsyncSuppressionsResource) -> None: self.delete = async_to_streamed_response_wrapper( suppressions.delete, ) - self.bulk_create = async_to_streamed_response_wrapper( - suppressions.bulk_create, - ) diff --git a/src/ark/resources/tenants.py b/src/ark/resources/tenants/tenants.py similarity index 74% rename from src/ark/resources/tenants.py rename to src/ark/resources/tenants/tenants.py index e7b13a1..684cf1a 100644 --- a/src/ark/resources/tenants.py +++ b/src/ark/resources/tenants/tenants.py @@ -7,29 +7,101 @@ import httpx -from ..types import tenant_list_params, tenant_create_params, tenant_update_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( +from .usage import ( + UsageResource, + AsyncUsageResource, + UsageResourceWithRawResponse, + AsyncUsageResourceWithRawResponse, + UsageResourceWithStreamingResponse, + AsyncUsageResourceWithStreamingResponse, +) +from ...types import tenant_list_params, tenant_create_params, tenant_update_params +from .domains import ( + DomainsResource, + AsyncDomainsResource, + DomainsResourceWithRawResponse, + AsyncDomainsResourceWithRawResponse, + DomainsResourceWithStreamingResponse, + AsyncDomainsResourceWithStreamingResponse, +) +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from .tracking import ( + TrackingResource, + AsyncTrackingResource, + TrackingResourceWithRawResponse, + AsyncTrackingResourceWithRawResponse, + TrackingResourceWithStreamingResponse, + AsyncTrackingResourceWithStreamingResponse, +) +from .webhooks import ( + WebhooksResource, + AsyncWebhooksResource, + WebhooksResourceWithRawResponse, + AsyncWebhooksResourceWithRawResponse, + WebhooksResourceWithStreamingResponse, + AsyncWebhooksResourceWithStreamingResponse, +) +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..pagination import SyncPageNumberPagination, AsyncPageNumberPagination -from .._base_client import AsyncPaginator, make_request_options -from ..types.tenant import Tenant -from ..types.tenant_create_response import TenantCreateResponse -from ..types.tenant_delete_response import TenantDeleteResponse -from ..types.tenant_update_response import TenantUpdateResponse -from ..types.tenant_retrieve_response import TenantRetrieveResponse +from .credentials import ( + CredentialsResource, + AsyncCredentialsResource, + CredentialsResourceWithRawResponse, + AsyncCredentialsResourceWithRawResponse, + CredentialsResourceWithStreamingResponse, + AsyncCredentialsResourceWithStreamingResponse, +) +from ...pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from .suppressions import ( + SuppressionsResource, + AsyncSuppressionsResource, + SuppressionsResourceWithRawResponse, + AsyncSuppressionsResourceWithRawResponse, + SuppressionsResourceWithStreamingResponse, + AsyncSuppressionsResourceWithStreamingResponse, +) +from ..._base_client import AsyncPaginator, make_request_options +from ...types.tenant import Tenant +from ...types.tenant_create_response import TenantCreateResponse +from ...types.tenant_delete_response import TenantDeleteResponse +from ...types.tenant_update_response import TenantUpdateResponse +from ...types.tenant_retrieve_response import TenantRetrieveResponse __all__ = ["TenantsResource", "AsyncTenantsResource"] class TenantsResource(SyncAPIResource): + @cached_property + def credentials(self) -> CredentialsResource: + return CredentialsResource(self._client) + + @cached_property + def domains(self) -> DomainsResource: + return DomainsResource(self._client) + + @cached_property + def suppressions(self) -> SuppressionsResource: + return SuppressionsResource(self._client) + + @cached_property + def webhooks(self) -> WebhooksResource: + return WebhooksResource(self._client) + + @cached_property + def tracking(self) -> TrackingResource: + return TrackingResource(self._client) + + @cached_property + def usage(self) -> UsageResource: + return UsageResource(self._client) + @cached_property def with_raw_response(self) -> TenantsResourceWithRawResponse: """ @@ -284,6 +356,30 @@ def delete( class AsyncTenantsResource(AsyncAPIResource): + @cached_property + def credentials(self) -> AsyncCredentialsResource: + return AsyncCredentialsResource(self._client) + + @cached_property + def domains(self) -> AsyncDomainsResource: + return AsyncDomainsResource(self._client) + + @cached_property + def suppressions(self) -> AsyncSuppressionsResource: + return AsyncSuppressionsResource(self._client) + + @cached_property + def webhooks(self) -> AsyncWebhooksResource: + return AsyncWebhooksResource(self._client) + + @cached_property + def tracking(self) -> AsyncTrackingResource: + return AsyncTrackingResource(self._client) + + @cached_property + def usage(self) -> AsyncUsageResource: + return AsyncUsageResource(self._client) + @cached_property def with_raw_response(self) -> AsyncTenantsResourceWithRawResponse: """ @@ -557,6 +653,30 @@ def __init__(self, tenants: TenantsResource) -> None: tenants.delete, ) + @cached_property + def credentials(self) -> CredentialsResourceWithRawResponse: + return CredentialsResourceWithRawResponse(self._tenants.credentials) + + @cached_property + def domains(self) -> DomainsResourceWithRawResponse: + return DomainsResourceWithRawResponse(self._tenants.domains) + + @cached_property + def suppressions(self) -> SuppressionsResourceWithRawResponse: + return SuppressionsResourceWithRawResponse(self._tenants.suppressions) + + @cached_property + def webhooks(self) -> WebhooksResourceWithRawResponse: + return WebhooksResourceWithRawResponse(self._tenants.webhooks) + + @cached_property + def tracking(self) -> TrackingResourceWithRawResponse: + return TrackingResourceWithRawResponse(self._tenants.tracking) + + @cached_property + def usage(self) -> UsageResourceWithRawResponse: + return UsageResourceWithRawResponse(self._tenants.usage) + class AsyncTenantsResourceWithRawResponse: def __init__(self, tenants: AsyncTenantsResource) -> None: @@ -578,6 +698,30 @@ def __init__(self, tenants: AsyncTenantsResource) -> None: tenants.delete, ) + @cached_property + def credentials(self) -> AsyncCredentialsResourceWithRawResponse: + return AsyncCredentialsResourceWithRawResponse(self._tenants.credentials) + + @cached_property + def domains(self) -> AsyncDomainsResourceWithRawResponse: + return AsyncDomainsResourceWithRawResponse(self._tenants.domains) + + @cached_property + def suppressions(self) -> AsyncSuppressionsResourceWithRawResponse: + return AsyncSuppressionsResourceWithRawResponse(self._tenants.suppressions) + + @cached_property + def webhooks(self) -> AsyncWebhooksResourceWithRawResponse: + return AsyncWebhooksResourceWithRawResponse(self._tenants.webhooks) + + @cached_property + def tracking(self) -> AsyncTrackingResourceWithRawResponse: + return AsyncTrackingResourceWithRawResponse(self._tenants.tracking) + + @cached_property + def usage(self) -> AsyncUsageResourceWithRawResponse: + return AsyncUsageResourceWithRawResponse(self._tenants.usage) + class TenantsResourceWithStreamingResponse: def __init__(self, tenants: TenantsResource) -> None: @@ -599,6 +743,30 @@ def __init__(self, tenants: TenantsResource) -> None: tenants.delete, ) + @cached_property + def credentials(self) -> CredentialsResourceWithStreamingResponse: + return CredentialsResourceWithStreamingResponse(self._tenants.credentials) + + @cached_property + def domains(self) -> DomainsResourceWithStreamingResponse: + return DomainsResourceWithStreamingResponse(self._tenants.domains) + + @cached_property + def suppressions(self) -> SuppressionsResourceWithStreamingResponse: + return SuppressionsResourceWithStreamingResponse(self._tenants.suppressions) + + @cached_property + def webhooks(self) -> WebhooksResourceWithStreamingResponse: + return WebhooksResourceWithStreamingResponse(self._tenants.webhooks) + + @cached_property + def tracking(self) -> TrackingResourceWithStreamingResponse: + return TrackingResourceWithStreamingResponse(self._tenants.tracking) + + @cached_property + def usage(self) -> UsageResourceWithStreamingResponse: + return UsageResourceWithStreamingResponse(self._tenants.usage) + class AsyncTenantsResourceWithStreamingResponse: def __init__(self, tenants: AsyncTenantsResource) -> None: @@ -619,3 +787,27 @@ def __init__(self, tenants: AsyncTenantsResource) -> None: self.delete = async_to_streamed_response_wrapper( tenants.delete, ) + + @cached_property + def credentials(self) -> AsyncCredentialsResourceWithStreamingResponse: + return AsyncCredentialsResourceWithStreamingResponse(self._tenants.credentials) + + @cached_property + def domains(self) -> AsyncDomainsResourceWithStreamingResponse: + return AsyncDomainsResourceWithStreamingResponse(self._tenants.domains) + + @cached_property + def suppressions(self) -> AsyncSuppressionsResourceWithStreamingResponse: + return AsyncSuppressionsResourceWithStreamingResponse(self._tenants.suppressions) + + @cached_property + def webhooks(self) -> AsyncWebhooksResourceWithStreamingResponse: + return AsyncWebhooksResourceWithStreamingResponse(self._tenants.webhooks) + + @cached_property + def tracking(self) -> AsyncTrackingResourceWithStreamingResponse: + return AsyncTrackingResourceWithStreamingResponse(self._tenants.tracking) + + @cached_property + def usage(self) -> AsyncUsageResourceWithStreamingResponse: + return AsyncUsageResourceWithStreamingResponse(self._tenants.usage) diff --git a/src/ark/resources/tracking.py b/src/ark/resources/tenants/tracking.py similarity index 84% rename from src/ark/resources/tracking.py rename to src/ark/resources/tenants/tracking.py index ddcbdd5..995292f 100644 --- a/src/ark/resources/tracking.py +++ b/src/ark/resources/tenants/tracking.py @@ -6,24 +6,24 @@ import httpx -from ..types import tracking_create_params, tracking_update_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options -from ..types.tracking_list_response import TrackingListResponse -from ..types.tracking_create_response import TrackingCreateResponse -from ..types.tracking_delete_response import TrackingDeleteResponse -from ..types.tracking_update_response import TrackingUpdateResponse -from ..types.tracking_verify_response import TrackingVerifyResponse -from ..types.tracking_retrieve_response import TrackingRetrieveResponse +from ..._base_client import make_request_options +from ...types.tenants import tracking_create_params, tracking_update_params +from ...types.tenants.tracking_list_response import TrackingListResponse +from ...types.tenants.tracking_create_response import TrackingCreateResponse +from ...types.tenants.tracking_delete_response import TrackingDeleteResponse +from ...types.tenants.tracking_update_response import TrackingUpdateResponse +from ...types.tenants.tracking_verify_response import TrackingVerifyResponse +from ...types.tenants.tracking_retrieve_response import TrackingRetrieveResponse __all__ = ["TrackingResource", "AsyncTrackingResource"] @@ -50,6 +50,7 @@ def with_streaming_response(self) -> TrackingResourceWithStreamingResponse: def create( self, + tenant_id: str, *, domain_id: int, name: str, @@ -64,7 +65,7 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TrackingCreateResponse: """ - Create a new track domain for open/click tracking. + Create a new track domain for open/click tracking for a tenant. After creation, you must configure a CNAME record pointing to the provided DNS value before tracking will work. @@ -88,8 +89,10 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._post( - "/tracking", + f"/tenants/{tenant_id}/tracking", body=maybe_transform( { "domain_id": domain_id, @@ -110,6 +113,7 @@ def retrieve( self, tracking_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -118,7 +122,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TrackingRetrieveResponse: """ - Get details of a specific track domain including DNS configuration + Get details of a specific track domain including DNS configuration. Args: extra_headers: Send extra headers @@ -129,10 +133,12 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return self._get( - f"/tracking/{tracking_id}", + f"/tenants/{tenant_id}/tracking/{tracking_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -143,6 +149,7 @@ def update( self, tracking_id: str, *, + tenant_id: str, excluded_click_domains: Optional[str] | Omit = omit, ssl_enabled: Optional[bool] | Omit = omit, track_clicks: Optional[bool] | Omit = omit, @@ -181,10 +188,12 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return self._patch( - f"/tracking/{tracking_id}", + f"/tenants/{tenant_id}/tracking/{tracking_id}", body=maybe_transform( { "excluded_click_domains": excluded_click_domains, @@ -202,6 +211,7 @@ def update( def list( self, + tenant_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -210,13 +220,24 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TrackingListResponse: - """List all track domains configured for your server. + """List all track domains configured for a tenant. Track domains enable open and - click tracking for your emails. + click tracking for emails. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._get( - "/tracking", + f"/tenants/{tenant_id}/tracking", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -227,6 +248,7 @@ def delete( self, tracking_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -248,10 +270,12 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return self._delete( - f"/tracking/{tracking_id}", + f"/tenants/{tenant_id}/tracking/{tracking_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -262,6 +286,7 @@ def verify( self, tracking_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -284,10 +309,12 @@ def verify( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return self._post( - f"/tracking/{tracking_id}/verify", + f"/tenants/{tenant_id}/tracking/{tracking_id}/verify", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -317,6 +344,7 @@ def with_streaming_response(self) -> AsyncTrackingResourceWithStreamingResponse: async def create( self, + tenant_id: str, *, domain_id: int, name: str, @@ -331,7 +359,7 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TrackingCreateResponse: """ - Create a new track domain for open/click tracking. + Create a new track domain for open/click tracking for a tenant. After creation, you must configure a CNAME record pointing to the provided DNS value before tracking will work. @@ -355,8 +383,10 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._post( - "/tracking", + f"/tenants/{tenant_id}/tracking", body=await async_maybe_transform( { "domain_id": domain_id, @@ -377,6 +407,7 @@ async def retrieve( self, tracking_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -385,7 +416,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TrackingRetrieveResponse: """ - Get details of a specific track domain including DNS configuration + Get details of a specific track domain including DNS configuration. Args: extra_headers: Send extra headers @@ -396,10 +427,12 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return await self._get( - f"/tracking/{tracking_id}", + f"/tenants/{tenant_id}/tracking/{tracking_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -410,6 +443,7 @@ async def update( self, tracking_id: str, *, + tenant_id: str, excluded_click_domains: Optional[str] | Omit = omit, ssl_enabled: Optional[bool] | Omit = omit, track_clicks: Optional[bool] | Omit = omit, @@ -448,10 +482,12 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return await self._patch( - f"/tracking/{tracking_id}", + f"/tenants/{tenant_id}/tracking/{tracking_id}", body=await async_maybe_transform( { "excluded_click_domains": excluded_click_domains, @@ -469,6 +505,7 @@ async def update( async def list( self, + tenant_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -477,13 +514,24 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TrackingListResponse: - """List all track domains configured for your server. + """List all track domains configured for a tenant. Track domains enable open and - click tracking for your emails. + click tracking for emails. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._get( - "/tracking", + f"/tenants/{tenant_id}/tracking", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -494,6 +542,7 @@ async def delete( self, tracking_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -515,10 +564,12 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return await self._delete( - f"/tracking/{tracking_id}", + f"/tenants/{tenant_id}/tracking/{tracking_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -529,6 +580,7 @@ async def verify( self, tracking_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -551,10 +603,12 @@ async def verify( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not tracking_id: raise ValueError(f"Expected a non-empty value for `tracking_id` but received {tracking_id!r}") return await self._post( - f"/tracking/{tracking_id}/verify", + f"/tenants/{tenant_id}/tracking/{tracking_id}/verify", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/ark/resources/tenants/usage.py b/src/ark/resources/tenants/usage.py new file mode 100644 index 0000000..ce82b19 --- /dev/null +++ b/src/ark/resources/tenants/usage.py @@ -0,0 +1,402 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.tenants import usage_retrieve_params, usage_retrieve_timeseries_params +from ...types.tenants.usage_retrieve_response import UsageRetrieveResponse +from ...types.tenants.usage_retrieve_timeseries_response import UsageRetrieveTimeseriesResponse + +__all__ = ["UsageResource", "AsyncUsageResource"] + + +class UsageResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> UsageResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return UsageResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsageResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return UsageResourceWithStreamingResponse(self) + + def retrieve( + self, + tenant_id: str, + *, + period: str | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageRetrieveResponse: + """ + Returns email sending statistics for a specific tenant over a time period. + + **Use cases:** + + - Display usage dashboard to your customers + - Calculate per-tenant billing + - Monitor tenant health and delivery rates + + **Period formats:** + + - Shortcuts: `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + - Month: `2024-01` (full month) + - Date range: `2024-01-01..2024-01-31` + - Single day: `2024-01-15` + + **Response includes:** + + - `emails` - Counts for sent, delivered, soft_failed, hard_failed, bounced, held + - `rates` - Delivery rate and bounce rate as decimals (0.95 = 95%) + + Args: + period: Time period for usage data. Defaults to current month. + + **Formats:** + + - Shortcuts: `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + - Month: `2024-01` + - Range: `2024-01-01..2024-01-31` + - Day: `2024-01-15` + + timezone: Timezone for period calculations (IANA format). Defaults to UTC. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._get( + f"/tenants/{tenant_id}/usage", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "period": period, + "timezone": timezone, + }, + usage_retrieve_params.UsageRetrieveParams, + ), + ), + cast_to=UsageRetrieveResponse, + ) + + def retrieve_timeseries( + self, + tenant_id: str, + *, + granularity: Literal["hour", "day", "week", "month"] | Omit = omit, + period: str | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageRetrieveTimeseriesResponse: + """ + Returns time-bucketed email statistics for a specific tenant. + + **Use cases:** + + - Build usage charts and graphs + - Identify sending patterns + - Detect anomalies in delivery rates + + **Granularity options:** + + - `hour` - Hourly buckets (best for last 7 days) + - `day` - Daily buckets (best for last 30-90 days) + - `week` - Weekly buckets (best for last 6 months) + - `month` - Monthly buckets (best for year-over-year) + + The response includes a data point for each time bucket with all email metrics. + + Args: + granularity: Time bucket size for data points + + period: Time period for timeseries data. Defaults to current month. + + timezone: Timezone for period calculations (IANA format). Defaults to UTC. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return self._get( + f"/tenants/{tenant_id}/usage/timeseries", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "granularity": granularity, + "period": period, + "timezone": timezone, + }, + usage_retrieve_timeseries_params.UsageRetrieveTimeseriesParams, + ), + ), + cast_to=UsageRetrieveTimeseriesResponse, + ) + + +class AsyncUsageResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncUsageResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsageResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsageResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/ArkHQ-io/ark-python#with_streaming_response + """ + return AsyncUsageResourceWithStreamingResponse(self) + + async def retrieve( + self, + tenant_id: str, + *, + period: str | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageRetrieveResponse: + """ + Returns email sending statistics for a specific tenant over a time period. + + **Use cases:** + + - Display usage dashboard to your customers + - Calculate per-tenant billing + - Monitor tenant health and delivery rates + + **Period formats:** + + - Shortcuts: `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + - Month: `2024-01` (full month) + - Date range: `2024-01-01..2024-01-31` + - Single day: `2024-01-15` + + **Response includes:** + + - `emails` - Counts for sent, delivered, soft_failed, hard_failed, bounced, held + - `rates` - Delivery rate and bounce rate as decimals (0.95 = 95%) + + Args: + period: Time period for usage data. Defaults to current month. + + **Formats:** + + - Shortcuts: `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + - Month: `2024-01` + - Range: `2024-01-01..2024-01-31` + - Day: `2024-01-15` + + timezone: Timezone for period calculations (IANA format). Defaults to UTC. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return await self._get( + f"/tenants/{tenant_id}/usage", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "period": period, + "timezone": timezone, + }, + usage_retrieve_params.UsageRetrieveParams, + ), + ), + cast_to=UsageRetrieveResponse, + ) + + async def retrieve_timeseries( + self, + tenant_id: str, + *, + granularity: Literal["hour", "day", "week", "month"] | Omit = omit, + period: str | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageRetrieveTimeseriesResponse: + """ + Returns time-bucketed email statistics for a specific tenant. + + **Use cases:** + + - Build usage charts and graphs + - Identify sending patterns + - Detect anomalies in delivery rates + + **Granularity options:** + + - `hour` - Hourly buckets (best for last 7 days) + - `day` - Daily buckets (best for last 30-90 days) + - `week` - Weekly buckets (best for last 6 months) + - `month` - Monthly buckets (best for year-over-year) + + The response includes a data point for each time bucket with all email metrics. + + Args: + granularity: Time bucket size for data points + + period: Time period for timeseries data. Defaults to current month. + + timezone: Timezone for period calculations (IANA format). Defaults to UTC. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") + return await self._get( + f"/tenants/{tenant_id}/usage/timeseries", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "granularity": granularity, + "period": period, + "timezone": timezone, + }, + usage_retrieve_timeseries_params.UsageRetrieveTimeseriesParams, + ), + ), + cast_to=UsageRetrieveTimeseriesResponse, + ) + + +class UsageResourceWithRawResponse: + def __init__(self, usage: UsageResource) -> None: + self._usage = usage + + self.retrieve = to_raw_response_wrapper( + usage.retrieve, + ) + self.retrieve_timeseries = to_raw_response_wrapper( + usage.retrieve_timeseries, + ) + + +class AsyncUsageResourceWithRawResponse: + def __init__(self, usage: AsyncUsageResource) -> None: + self._usage = usage + + self.retrieve = async_to_raw_response_wrapper( + usage.retrieve, + ) + self.retrieve_timeseries = async_to_raw_response_wrapper( + usage.retrieve_timeseries, + ) + + +class UsageResourceWithStreamingResponse: + def __init__(self, usage: UsageResource) -> None: + self._usage = usage + + self.retrieve = to_streamed_response_wrapper( + usage.retrieve, + ) + self.retrieve_timeseries = to_streamed_response_wrapper( + usage.retrieve_timeseries, + ) + + +class AsyncUsageResourceWithStreamingResponse: + def __init__(self, usage: AsyncUsageResource) -> None: + self._usage = usage + + self.retrieve = async_to_streamed_response_wrapper( + usage.retrieve, + ) + self.retrieve_timeseries = async_to_streamed_response_wrapper( + usage.retrieve_timeseries, + ) diff --git a/src/ark/resources/webhooks.py b/src/ark/resources/tenants/webhooks.py similarity index 87% rename from src/ark/resources/webhooks.py rename to src/ark/resources/tenants/webhooks.py index 97b0202..ee3a683 100644 --- a/src/ark/resources/webhooks.py +++ b/src/ark/resources/tenants/webhooks.py @@ -7,27 +7,32 @@ import httpx -from ..types import webhook_test_params, webhook_create_params, webhook_update_params, webhook_list_deliveries_params -from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options -from ..types.webhook_list_response import WebhookListResponse -from ..types.webhook_test_response import WebhookTestResponse -from ..types.webhook_create_response import WebhookCreateResponse -from ..types.webhook_delete_response import WebhookDeleteResponse -from ..types.webhook_update_response import WebhookUpdateResponse -from ..types.webhook_retrieve_response import WebhookRetrieveResponse -from ..types.webhook_list_deliveries_response import WebhookListDeliveriesResponse -from ..types.webhook_replay_delivery_response import WebhookReplayDeliveryResponse -from ..types.webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse +from ..._base_client import make_request_options +from ...types.tenants import ( + webhook_test_params, + webhook_create_params, + webhook_update_params, + webhook_list_deliveries_params, +) +from ...types.tenants.webhook_list_response import WebhookListResponse +from ...types.tenants.webhook_test_response import WebhookTestResponse +from ...types.tenants.webhook_create_response import WebhookCreateResponse +from ...types.tenants.webhook_delete_response import WebhookDeleteResponse +from ...types.tenants.webhook_update_response import WebhookUpdateResponse +from ...types.tenants.webhook_retrieve_response import WebhookRetrieveResponse +from ...types.tenants.webhook_list_deliveries_response import WebhookListDeliveriesResponse +from ...types.tenants.webhook_replay_delivery_response import WebhookReplayDeliveryResponse +from ...types.tenants.webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse __all__ = ["WebhooksResource", "AsyncWebhooksResource"] @@ -54,6 +59,7 @@ def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse: def create( self, + tenant_id: str, *, name: str, url: str, @@ -82,7 +88,7 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WebhookCreateResponse: """ - Create a webhook endpoint to receive email event notifications. + Create a webhook endpoint to receive email event notifications for a tenant. **Available events:** @@ -124,8 +130,10 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._post( - "/webhooks", + f"/tenants/{tenant_id}/webhooks", body=maybe_transform( { "name": name, @@ -146,6 +154,7 @@ def retrieve( self, webhook_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -165,10 +174,12 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return self._get( - f"/webhooks/{webhook_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -179,6 +190,7 @@ def update( self, webhook_id: str, *, + tenant_id: str, all_events: Optional[bool] | Omit = omit, enabled: Optional[bool] | Omit = omit, events: Optional[SequenceNotStr[str]] | Omit = omit, @@ -203,10 +215,12 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return self._patch( - f"/webhooks/{webhook_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}", body=maybe_transform( { "all_events": all_events, @@ -225,6 +239,7 @@ def update( def list( self, + tenant_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -233,9 +248,22 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WebhookListResponse: - """Get all configured webhook endpoints""" + """ + Get all configured webhook endpoints for a tenant. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return self._get( - "/webhooks", + f"/tenants/{tenant_id}/webhooks", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -246,6 +274,7 @@ def delete( self, webhook_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -265,10 +294,12 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return self._delete( - f"/webhooks/{webhook_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -279,6 +310,7 @@ def list_deliveries( self, webhook_id: str, *, + tenant_id: str, after: int | Omit = omit, before: int | Omit = omit, event: Literal[ @@ -341,10 +373,12 @@ def list_deliveries( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return self._get( - f"/webhooks/{webhook_id}/deliveries", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -369,6 +403,7 @@ def replay_delivery( self, delivery_id: str, *, + tenant_id: str, webhook_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -404,12 +439,14 @@ def replay_delivery( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") if not delivery_id: raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") return self._post( - f"/webhooks/{webhook_id}/deliveries/{delivery_id}/replay", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -420,6 +457,7 @@ def retrieve_delivery( self, delivery_id: str, *, + tenant_id: str, webhook_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -449,12 +487,14 @@ def retrieve_delivery( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") if not delivery_id: raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") return self._get( - f"/webhooks/{webhook_id}/deliveries/{delivery_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries/{delivery_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -465,6 +505,7 @@ def test( self, webhook_id: str, *, + tenant_id: str, event: Literal[ "MessageSent", "MessageDelayed", @@ -507,10 +548,12 @@ def test( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return self._post( - f"/webhooks/{webhook_id}/test", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/test", body=maybe_transform({"event": event}, webhook_test_params.WebhookTestParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -541,6 +584,7 @@ def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse: async def create( self, + tenant_id: str, *, name: str, url: str, @@ -569,7 +613,7 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WebhookCreateResponse: """ - Create a webhook endpoint to receive email event notifications. + Create a webhook endpoint to receive email event notifications for a tenant. **Available events:** @@ -611,8 +655,10 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._post( - "/webhooks", + f"/tenants/{tenant_id}/webhooks", body=await async_maybe_transform( { "name": name, @@ -633,6 +679,7 @@ async def retrieve( self, webhook_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -652,10 +699,12 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return await self._get( - f"/webhooks/{webhook_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -666,6 +715,7 @@ async def update( self, webhook_id: str, *, + tenant_id: str, all_events: Optional[bool] | Omit = omit, enabled: Optional[bool] | Omit = omit, events: Optional[SequenceNotStr[str]] | Omit = omit, @@ -690,10 +740,12 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return await self._patch( - f"/webhooks/{webhook_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}", body=await async_maybe_transform( { "all_events": all_events, @@ -712,6 +764,7 @@ async def update( async def list( self, + tenant_id: str, *, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -720,9 +773,22 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WebhookListResponse: - """Get all configured webhook endpoints""" + """ + Get all configured webhook endpoints for a tenant. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") return await self._get( - "/webhooks", + f"/tenants/{tenant_id}/webhooks", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -733,6 +799,7 @@ async def delete( self, webhook_id: str, *, + tenant_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -752,10 +819,12 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return await self._delete( - f"/webhooks/{webhook_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -766,6 +835,7 @@ async def list_deliveries( self, webhook_id: str, *, + tenant_id: str, after: int | Omit = omit, before: int | Omit = omit, event: Literal[ @@ -828,10 +898,12 @@ async def list_deliveries( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return await self._get( - f"/webhooks/{webhook_id}/deliveries", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -856,6 +928,7 @@ async def replay_delivery( self, delivery_id: str, *, + tenant_id: str, webhook_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -891,12 +964,14 @@ async def replay_delivery( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") if not delivery_id: raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") return await self._post( - f"/webhooks/{webhook_id}/deliveries/{delivery_id}/replay", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -907,6 +982,7 @@ async def retrieve_delivery( self, delivery_id: str, *, + tenant_id: str, webhook_id: str, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -936,12 +1012,14 @@ async def retrieve_delivery( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") if not delivery_id: raise ValueError(f"Expected a non-empty value for `delivery_id` but received {delivery_id!r}") return await self._get( - f"/webhooks/{webhook_id}/deliveries/{delivery_id}", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries/{delivery_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -952,6 +1030,7 @@ async def test( self, webhook_id: str, *, + tenant_id: str, event: Literal[ "MessageSent", "MessageDelayed", @@ -994,10 +1073,12 @@ async def test( timeout: Override the client-level default timeout for this request, in seconds """ + if not tenant_id: + raise ValueError(f"Expected a non-empty value for `tenant_id` but received {tenant_id!r}") if not webhook_id: raise ValueError(f"Expected a non-empty value for `webhook_id` but received {webhook_id!r}") return await self._post( - f"/webhooks/{webhook_id}/test", + f"/tenants/{tenant_id}/webhooks/{webhook_id}/test", body=await async_maybe_transform({"event": event}, webhook_test_params.WebhookTestParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout diff --git a/src/ark/resources/usage.py b/src/ark/resources/usage.py index cb9004f..9a38220 100644 --- a/src/ark/resources/usage.py +++ b/src/ark/resources/usage.py @@ -2,9 +2,13 @@ from __future__ import annotations +from typing_extensions import Literal + import httpx -from .._types import Body, Query, Headers, NotGiven, not_given +from ..types import usage_export_params, usage_retrieve_params, usage_list_tenants_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -13,8 +17,11 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options -from ..types.usage_retrieve_response import UsageRetrieveResponse +from ..pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from .._base_client import AsyncPaginator, make_request_options +from ..types.org_usage_summary import OrgUsageSummary +from ..types.tenant_usage_item import TenantUsageItem +from ..types.usage_export_response import UsageExportResponse __all__ = ["UsageResource", "AsyncUsageResource"] @@ -42,41 +49,271 @@ def with_streaming_response(self) -> UsageResourceWithStreamingResponse: def retrieve( self, *, + period: str | Omit = omit, + timezone: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> UsageRetrieveResponse: - """ - Returns current usage and limit information for your account. + ) -> OrgUsageSummary: + """Returns aggregated email sending statistics for your entire organization. + + For + per-tenant breakdown, use `GET /usage/tenants`. - This endpoint is designed for: + **Use cases:** - - **AI agents/MCP servers:** Check constraints before planning batch operations - - **Monitoring dashboards:** Display current usage status - - **Rate limit awareness:** Know remaining capacity before making requests + - Platform dashboards showing org-wide metrics + - Quick health check on overall sending + - Monitoring total volume and delivery rates **Response includes:** - - `rateLimit` - API request rate limit (requests per second) - - `sendLimit` - Email sending limit (emails per hour) - - `billing` - Credit balance and auto-recharge configuration + - `emails` - Aggregated email counts across all tenants + - `rates` - Overall delivery and bounce rates + - `tenants` - Tenant count summary (total, active, with activity) + + **Related endpoints:** + + - `GET /usage/tenants` - Paginated usage per tenant + - `GET /usage/export` - Export usage data for billing + - `GET /tenants/{tenantId}/usage` - Single tenant usage details + - `GET /limits` - Rate limits and send limits + + Args: + period: Time period for usage data. + + **Shortcuts:** `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + + timezone: Timezone for period calculations (IANA format) + + extra_headers: Send extra headers - **Notes:** + extra_query: Add additional query parameters to the request - - This request counts against your rate limit - - `sendLimit` may be null if Postal is temporarily unavailable - - `billing` is null if billing is not configured - - Send limit resets at the top of each hour + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ return self._get( "/usage", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "period": period, + "timezone": timezone, + }, + usage_retrieve_params.UsageRetrieveParams, + ), ), - cast_to=UsageRetrieveResponse, + cast_to=OrgUsageSummary, + ) + + def export( + self, + *, + format: Literal["csv", "jsonl"] | Omit = omit, + min_sent: int | Omit = omit, + period: str | Omit = omit, + status: Literal["active", "suspended", "archived"] | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageExportResponse: + """Export email usage data for all tenants in CSV or JSON Lines format. + + Designed + for billing system integration, data warehousing, and analytics. + + **Jobs to be done:** + + - Import usage data into billing systems (Stripe, Chargebee, etc.) + - Load into data warehouses (Snowflake, BigQuery, etc.) + - Process in spreadsheets (Excel, Google Sheets) + - Feed into BI tools (Looker, Metabase, etc.) + + **Export formats:** + + - `csv` - UTF-8 with BOM for Excel compatibility (default) + - `jsonl` - JSON Lines (one JSON object per line, streamable) + + **CSV columns:** `tenant_id`, `tenant_name`, `external_id`, `status`, `sent`, + `delivered`, `soft_failed`, `hard_failed`, `bounced`, `held`, `delivery_rate`, + `bounce_rate`, `period_start`, `period_end` + + **Response headers:** + + - `Content-Disposition` - Filename for download + - `Content-Type` - `text/csv` or `application/x-ndjson` + + Args: + format: Export format + + min_sent: Only include tenants with at least this many emails sent + + period: Time period for export. + + **Shortcuts:** `this_month`, `last_month`, `last_30_days`, etc. + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + + status: Filter by tenant status + + timezone: Timezone for period calculations (IANA format) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/usage/export", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "format": format, + "min_sent": min_sent, + "period": period, + "status": status, + "timezone": timezone, + }, + usage_export_params.UsageExportParams, + ), + ), + cast_to=UsageExportResponse, + ) + + def list_tenants( + self, + *, + min_sent: int | Omit = omit, + page: int | Omit = omit, + period: str | Omit = omit, + per_page: int | Omit = omit, + sort: Literal[ + "sent", + "-sent", + "delivered", + "-delivered", + "bounce_rate", + "-bounce_rate", + "delivery_rate", + "-delivery_rate", + "tenant_name", + "-tenant_name", + ] + | Omit = omit, + status: Literal["active", "suspended", "archived"] | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncPageNumberPagination[TenantUsageItem]: + """Returns email usage statistics for all tenants in your organization. + + Results are + paginated with page-based navigation. + + **Jobs to be done:** + + - Generate monthly billing invoices per tenant + - Build admin dashboards showing all customer usage + - Identify high-volume or problematic tenants + - Track usage against plan limits + + **Sorting options:** + + - `sent`, `-sent` - Sort by emails sent (ascending/descending) + - `delivered`, `-delivered` - Sort by emails delivered + - `bounce_rate`, `-bounce_rate` - Sort by bounce rate + - `tenant_name`, `-tenant_name` - Sort alphabetically by tenant name + + **Filtering:** + + - `status` - Filter by tenant status (active, suspended, archived) + - `minSent` - Only include tenants with at least N emails sent + + **Auto-pagination:** SDKs support iterating over all pages automatically. + + Args: + min_sent: Only include tenants with at least this many emails sent + + page: Page number (1-indexed) + + period: Time period for usage data. Defaults to current month. + + **Shortcuts:** `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + + per_page: Results per page (max 100) + + sort: Sort order for results. Prefix with `-` for descending order. + + status: Filter by tenant status + + timezone: Timezone for period calculations (IANA format). Defaults to UTC. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/usage/tenants", + page=SyncPageNumberPagination[TenantUsageItem], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "min_sent": min_sent, + "page": page, + "period": period, + "per_page": per_page, + "sort": sort, + "status": status, + "timezone": timezone, + }, + usage_list_tenants_params.UsageListTenantsParams, + ), + ), + model=TenantUsageItem, ) @@ -103,41 +340,271 @@ def with_streaming_response(self) -> AsyncUsageResourceWithStreamingResponse: async def retrieve( self, *, + period: str | Omit = omit, + timezone: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> UsageRetrieveResponse: - """ - Returns current usage and limit information for your account. + ) -> OrgUsageSummary: + """Returns aggregated email sending statistics for your entire organization. + + For + per-tenant breakdown, use `GET /usage/tenants`. - This endpoint is designed for: + **Use cases:** - - **AI agents/MCP servers:** Check constraints before planning batch operations - - **Monitoring dashboards:** Display current usage status - - **Rate limit awareness:** Know remaining capacity before making requests + - Platform dashboards showing org-wide metrics + - Quick health check on overall sending + - Monitoring total volume and delivery rates **Response includes:** - - `rateLimit` - API request rate limit (requests per second) - - `sendLimit` - Email sending limit (emails per hour) - - `billing` - Credit balance and auto-recharge configuration + - `emails` - Aggregated email counts across all tenants + - `rates` - Overall delivery and bounce rates + - `tenants` - Tenant count summary (total, active, with activity) + + **Related endpoints:** + + - `GET /usage/tenants` - Paginated usage per tenant + - `GET /usage/export` - Export usage data for billing + - `GET /tenants/{tenantId}/usage` - Single tenant usage details + - `GET /limits` - Rate limits and send limits - **Notes:** + Args: + period: Time period for usage data. - - This request counts against your rate limit - - `sendLimit` may be null if Postal is temporarily unavailable - - `billing` is null if billing is not configured - - Send limit resets at the top of each hour + **Shortcuts:** `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + + timezone: Timezone for period calculations (IANA format) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ return await self._get( "/usage", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "period": period, + "timezone": timezone, + }, + usage_retrieve_params.UsageRetrieveParams, + ), + ), + cast_to=OrgUsageSummary, + ) + + async def export( + self, + *, + format: Literal["csv", "jsonl"] | Omit = omit, + min_sent: int | Omit = omit, + period: str | Omit = omit, + status: Literal["active", "suspended", "archived"] | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> UsageExportResponse: + """Export email usage data for all tenants in CSV or JSON Lines format. + + Designed + for billing system integration, data warehousing, and analytics. + + **Jobs to be done:** + + - Import usage data into billing systems (Stripe, Chargebee, etc.) + - Load into data warehouses (Snowflake, BigQuery, etc.) + - Process in spreadsheets (Excel, Google Sheets) + - Feed into BI tools (Looker, Metabase, etc.) + + **Export formats:** + + - `csv` - UTF-8 with BOM for Excel compatibility (default) + - `jsonl` - JSON Lines (one JSON object per line, streamable) + + **CSV columns:** `tenant_id`, `tenant_name`, `external_id`, `status`, `sent`, + `delivered`, `soft_failed`, `hard_failed`, `bounced`, `held`, `delivery_rate`, + `bounce_rate`, `period_start`, `period_end` + + **Response headers:** + + - `Content-Disposition` - Filename for download + - `Content-Type` - `text/csv` or `application/x-ndjson` + + Args: + format: Export format + + min_sent: Only include tenants with at least this many emails sent + + period: Time period for export. + + **Shortcuts:** `this_month`, `last_month`, `last_30_days`, etc. + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + + status: Filter by tenant status + + timezone: Timezone for period calculations (IANA format) + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/usage/export", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "format": format, + "min_sent": min_sent, + "period": period, + "status": status, + "timezone": timezone, + }, + usage_export_params.UsageExportParams, + ), + ), + cast_to=UsageExportResponse, + ) + + def list_tenants( + self, + *, + min_sent: int | Omit = omit, + page: int | Omit = omit, + period: str | Omit = omit, + per_page: int | Omit = omit, + sort: Literal[ + "sent", + "-sent", + "delivered", + "-delivered", + "bounce_rate", + "-bounce_rate", + "delivery_rate", + "-delivery_rate", + "tenant_name", + "-tenant_name", + ] + | Omit = omit, + status: Literal["active", "suspended", "archived"] | Omit = omit, + timezone: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[TenantUsageItem, AsyncPageNumberPagination[TenantUsageItem]]: + """Returns email usage statistics for all tenants in your organization. + + Results are + paginated with page-based navigation. + + **Jobs to be done:** + + - Generate monthly billing invoices per tenant + - Build admin dashboards showing all customer usage + - Identify high-volume or problematic tenants + - Track usage against plan limits + + **Sorting options:** + + - `sent`, `-sent` - Sort by emails sent (ascending/descending) + - `delivered`, `-delivered` - Sort by emails delivered + - `bounce_rate`, `-bounce_rate` - Sort by bounce rate + - `tenant_name`, `-tenant_name` - Sort alphabetically by tenant name + + **Filtering:** + + - `status` - Filter by tenant status (active, suspended, archived) + - `minSent` - Only include tenants with at least N emails sent + + **Auto-pagination:** SDKs support iterating over all pages automatically. + + Args: + min_sent: Only include tenants with at least this many emails sent + + page: Page number (1-indexed) + + period: Time period for usage data. Defaults to current month. + + **Shortcuts:** `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + + per_page: Results per page (max 100) + + sort: Sort order for results. Prefix with `-` for descending order. + + status: Filter by tenant status + + timezone: Timezone for period calculations (IANA format). Defaults to UTC. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/usage/tenants", + page=AsyncPageNumberPagination[TenantUsageItem], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "min_sent": min_sent, + "page": page, + "period": period, + "per_page": per_page, + "sort": sort, + "status": status, + "timezone": timezone, + }, + usage_list_tenants_params.UsageListTenantsParams, + ), ), - cast_to=UsageRetrieveResponse, + model=TenantUsageItem, ) @@ -148,6 +615,12 @@ def __init__(self, usage: UsageResource) -> None: self.retrieve = to_raw_response_wrapper( usage.retrieve, ) + self.export = to_raw_response_wrapper( + usage.export, + ) + self.list_tenants = to_raw_response_wrapper( + usage.list_tenants, + ) class AsyncUsageResourceWithRawResponse: @@ -157,6 +630,12 @@ def __init__(self, usage: AsyncUsageResource) -> None: self.retrieve = async_to_raw_response_wrapper( usage.retrieve, ) + self.export = async_to_raw_response_wrapper( + usage.export, + ) + self.list_tenants = async_to_raw_response_wrapper( + usage.list_tenants, + ) class UsageResourceWithStreamingResponse: @@ -166,6 +645,12 @@ def __init__(self, usage: UsageResource) -> None: self.retrieve = to_streamed_response_wrapper( usage.retrieve, ) + self.export = to_streamed_response_wrapper( + usage.export, + ) + self.list_tenants = to_streamed_response_wrapper( + usage.list_tenants, + ) class AsyncUsageResourceWithStreamingResponse: @@ -175,3 +660,9 @@ def __init__(self, usage: AsyncUsageResource) -> None: self.retrieve = async_to_streamed_response_wrapper( usage.retrieve, ) + self.export = async_to_streamed_response_wrapper( + usage.export, + ) + self.list_tenants = async_to_streamed_response_wrapper( + usage.list_tenants, + ) diff --git a/src/ark/types/__init__.py b/src/ark/types/__init__.py index 23e9cb2..8d422a6 100644 --- a/src/ark/types/__init__.py +++ b/src/ark/types/__init__.py @@ -5,63 +5,36 @@ from .shared import APIMeta as APIMeta from .tenant import Tenant as Tenant from .log_entry import LogEntry as LogEntry -from .dns_record import DNSRecord as DNSRecord -from .track_domain import TrackDomain as TrackDomain +from .email_rates import EmailRates as EmailRates +from .limits_data import LimitsData as LimitsData +from .email_counts import EmailCounts as EmailCounts +from .usage_period import UsagePeriod as UsagePeriod from .log_list_params import LogListParams as LogListParams from .log_entry_detail import LogEntryDetail as LogEntryDetail from .email_list_params import EmailListParams as EmailListParams from .email_send_params import EmailSendParams as EmailSendParams +from .org_usage_summary import OrgUsageSummary as OrgUsageSummary +from .tenant_usage_item import TenantUsageItem as TenantUsageItem from .tenant_list_params import TenantListParams as TenantListParams from .email_list_response import EmailListResponse as EmailListResponse from .email_send_response import EmailSendResponse as EmailSendResponse -from .webhook_test_params import WebhookTestParams as WebhookTestParams -from .domain_create_params import DomainCreateParams as DomainCreateParams -from .domain_list_response import DomainListResponse as DomainListResponse +from .usage_export_params import UsageExportParams as UsageExportParams from .email_retry_response import EmailRetryResponse as EmailRetryResponse from .tenant_create_params import TenantCreateParams as TenantCreateParams from .tenant_update_params import TenantUpdateParams as TenantUpdateParams from .email_retrieve_params import EmailRetrieveParams as EmailRetrieveParams from .email_send_raw_params import EmailSendRawParams as EmailSendRawParams from .log_retrieve_response import LogRetrieveResponse as LogRetrieveResponse -from .webhook_create_params import WebhookCreateParams as WebhookCreateParams -from .webhook_list_response import WebhookListResponse as WebhookListResponse -from .webhook_test_response import WebhookTestResponse as WebhookTestResponse -from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams -from .domain_create_response import DomainCreateResponse as DomainCreateResponse -from .domain_delete_response import DomainDeleteResponse as DomainDeleteResponse -from .domain_verify_response import DomainVerifyResponse as DomainVerifyResponse +from .usage_export_response import UsageExportResponse as UsageExportResponse +from .usage_retrieve_params import UsageRetrieveParams as UsageRetrieveParams from .tenant_create_response import TenantCreateResponse as TenantCreateResponse from .tenant_delete_response import TenantDeleteResponse as TenantDeleteResponse from .tenant_update_response import TenantUpdateResponse as TenantUpdateResponse -from .tracking_create_params import TrackingCreateParams as TrackingCreateParams -from .tracking_list_response import TrackingListResponse as TrackingListResponse -from .tracking_update_params import TrackingUpdateParams as TrackingUpdateParams from .email_retrieve_response import EmailRetrieveResponse as EmailRetrieveResponse from .email_send_batch_params import EmailSendBatchParams as EmailSendBatchParams from .email_send_raw_response import EmailSendRawResponse as EmailSendRawResponse -from .suppression_list_params import SuppressionListParams as SuppressionListParams -from .usage_retrieve_response import UsageRetrieveResponse as UsageRetrieveResponse -from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse -from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse -from .webhook_update_response import WebhookUpdateResponse as WebhookUpdateResponse -from .domain_retrieve_response import DomainRetrieveResponse as DomainRetrieveResponse +from .limit_retrieve_response import LimitRetrieveResponse as LimitRetrieveResponse from .tenant_retrieve_response import TenantRetrieveResponse as TenantRetrieveResponse -from .tracking_create_response import TrackingCreateResponse as TrackingCreateResponse -from .tracking_delete_response import TrackingDeleteResponse as TrackingDeleteResponse -from .tracking_update_response import TrackingUpdateResponse as TrackingUpdateResponse -from .tracking_verify_response import TrackingVerifyResponse as TrackingVerifyResponse from .email_send_batch_response import EmailSendBatchResponse as EmailSendBatchResponse -from .suppression_create_params import SuppressionCreateParams as SuppressionCreateParams -from .suppression_list_response import SuppressionListResponse as SuppressionListResponse -from .webhook_retrieve_response import WebhookRetrieveResponse as WebhookRetrieveResponse -from .tracking_retrieve_response import TrackingRetrieveResponse as TrackingRetrieveResponse -from .suppression_create_response import SuppressionCreateResponse as SuppressionCreateResponse -from .suppression_delete_response import SuppressionDeleteResponse as SuppressionDeleteResponse -from .suppression_retrieve_response import SuppressionRetrieveResponse as SuppressionRetrieveResponse -from .suppression_bulk_create_params import SuppressionBulkCreateParams as SuppressionBulkCreateParams -from .webhook_list_deliveries_params import WebhookListDeliveriesParams as WebhookListDeliveriesParams -from .suppression_bulk_create_response import SuppressionBulkCreateResponse as SuppressionBulkCreateResponse -from .webhook_list_deliveries_response import WebhookListDeliveriesResponse as WebhookListDeliveriesResponse -from .webhook_replay_delivery_response import WebhookReplayDeliveryResponse as WebhookReplayDeliveryResponse +from .usage_list_tenants_params import UsageListTenantsParams as UsageListTenantsParams from .email_retrieve_deliveries_response import EmailRetrieveDeliveriesResponse as EmailRetrieveDeliveriesResponse -from .webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse as WebhookRetrieveDeliveryResponse diff --git a/src/ark/types/email_counts.py b/src/ark/types/email_counts.py new file mode 100644 index 0000000..ffd5cf6 --- /dev/null +++ b/src/ark/types/email_counts.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["EmailCounts"] + + +class EmailCounts(BaseModel): + """Email delivery counts""" + + bounced: int + """Emails that bounced""" + + delivered: int + """Emails successfully delivered""" + + hard_failed: int + """Emails that hard-failed (permanent failures)""" + + held: int + """Emails currently held for review""" + + sent: int + """Total emails sent""" + + soft_failed: int + """Emails that soft-failed (temporary failures, may be retried)""" diff --git a/src/ark/types/email_rates.py b/src/ark/types/email_rates.py new file mode 100644 index 0000000..79450da --- /dev/null +++ b/src/ark/types/email_rates.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["EmailRates"] + + +class EmailRates(BaseModel): + """Email delivery rates (as decimals, e.g., 0.95 = 95%)""" + + bounce_rate: float + """Percentage of sent emails that bounced (0-1)""" + + delivery_rate: float + """Percentage of sent emails that were delivered (0-1)""" diff --git a/src/ark/types/email_retrieve_deliveries_response.py b/src/ark/types/email_retrieve_deliveries_response.py index a591b9d..a923d98 100644 --- a/src/ark/types/email_retrieve_deliveries_response.py +++ b/src/ark/types/email_retrieve_deliveries_response.py @@ -142,7 +142,7 @@ class Data(BaseModel): can_retry_manually: bool = FieldInfo(alias="canRetryManually") """ - Whether the message can be manually retried via `POST /emails/{id}/retry`. + Whether the message can be manually retried via `POST /emails/{emailId}/retry`. `true` when the raw message content is still available (not expired). Messages older than the retention period cannot be retried. """ diff --git a/src/ark/types/limit_retrieve_response.py b/src/ark/types/limit_retrieve_response.py new file mode 100644 index 0000000..06bd330 --- /dev/null +++ b/src/ark/types/limit_retrieve_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel +from .limits_data import LimitsData +from .shared.api_meta import APIMeta + +__all__ = ["LimitRetrieveResponse"] + + +class LimitRetrieveResponse(BaseModel): + """Account rate limits and send limits response""" + + data: LimitsData + """Current usage and limit information""" + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/usage_retrieve_response.py b/src/ark/types/limits_data.py similarity index 73% rename from src/ark/types/usage_retrieve_response.py rename to src/ark/types/limits_data.py index 86daa16..015054c 100644 --- a/src/ark/types/usage_retrieve_response.py +++ b/src/ark/types/limits_data.py @@ -7,12 +7,11 @@ from pydantic import Field as FieldInfo from .._models import BaseModel -from .shared.api_meta import APIMeta -__all__ = ["UsageRetrieveResponse", "Data", "DataBilling", "DataBillingAutoRecharge", "DataRateLimit", "DataSendLimit"] +__all__ = ["LimitsData", "Billing", "BillingAutoRecharge", "RateLimit", "SendLimit"] -class DataBillingAutoRecharge(BaseModel): +class BillingAutoRecharge(BaseModel): """Auto-recharge configuration""" amount: str @@ -25,10 +24,10 @@ class DataBillingAutoRecharge(BaseModel): """Balance threshold that triggers recharge""" -class DataBilling(BaseModel): +class Billing(BaseModel): """Billing and credit information""" - auto_recharge: DataBillingAutoRecharge = FieldInfo(alias="autoRecharge") + auto_recharge: BillingAutoRecharge = FieldInfo(alias="autoRecharge") """Auto-recharge configuration""" credit_balance: str = FieldInfo(alias="creditBalance") @@ -41,7 +40,7 @@ class DataBilling(BaseModel): """Whether a payment method is configured""" -class DataRateLimit(BaseModel): +class RateLimit(BaseModel): """API rate limit status""" limit: int @@ -57,7 +56,7 @@ class DataRateLimit(BaseModel): """Unix timestamp when the limit resets""" -class DataSendLimit(BaseModel): +class SendLimit(BaseModel): """Email send limit status (hourly cap)""" approaching: bool @@ -85,25 +84,14 @@ class DataSendLimit(BaseModel): """Emails sent in current period""" -class Data(BaseModel): +class LimitsData(BaseModel): """Current usage and limit information""" - billing: Optional[DataBilling] = None + billing: Optional[Billing] = None """Billing and credit information""" - rate_limit: DataRateLimit = FieldInfo(alias="rateLimit") + rate_limit: RateLimit = FieldInfo(alias="rateLimit") """API rate limit status""" - send_limit: Optional[DataSendLimit] = FieldInfo(alias="sendLimit", default=None) + send_limit: Optional[SendLimit] = FieldInfo(alias="sendLimit", default=None) """Email send limit status (hourly cap)""" - - -class UsageRetrieveResponse(BaseModel): - """Account usage and limits response""" - - data: Data - """Current usage and limit information""" - - meta: APIMeta - - success: Literal[True] diff --git a/src/ark/types/org_usage_summary.py b/src/ark/types/org_usage_summary.py new file mode 100644 index 0000000..ef35604 --- /dev/null +++ b/src/ark/types/org_usage_summary.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .email_rates import EmailRates +from .email_counts import EmailCounts +from .usage_period import UsagePeriod +from .shared.api_meta import APIMeta + +__all__ = ["OrgUsageSummary", "Data", "DataTenants"] + + +class DataTenants(BaseModel): + active: int + """Number of active tenants""" + + total: int + """Total number of tenants""" + + with_activity: int = FieldInfo(alias="withActivity") + """Number of tenants with sending activity""" + + +class Data(BaseModel): + emails: EmailCounts + """Email delivery counts""" + + period: UsagePeriod + """Time period for usage data""" + + rates: EmailRates + """Email delivery rates (as decimals, e.g., 0.95 = 95%)""" + + tenants: DataTenants + + +class OrgUsageSummary(BaseModel): + """Org-wide usage summary response""" + + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/platform/__init__.py b/src/ark/types/platform/__init__.py new file mode 100644 index 0000000..6e70e72 --- /dev/null +++ b/src/ark/types/platform/__init__.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .webhook_test_params import WebhookTestParams as WebhookTestParams +from .webhook_create_params import WebhookCreateParams as WebhookCreateParams +from .webhook_list_response import WebhookListResponse as WebhookListResponse +from .webhook_test_response import WebhookTestResponse as WebhookTestResponse +from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams +from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse +from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse +from .webhook_update_response import WebhookUpdateResponse as WebhookUpdateResponse +from .webhook_retrieve_response import WebhookRetrieveResponse as WebhookRetrieveResponse +from .webhook_list_deliveries_params import WebhookListDeliveriesParams as WebhookListDeliveriesParams +from .webhook_list_deliveries_response import WebhookListDeliveriesResponse as WebhookListDeliveriesResponse +from .webhook_replay_delivery_response import WebhookReplayDeliveryResponse as WebhookReplayDeliveryResponse +from .webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse as WebhookRetrieveDeliveryResponse diff --git a/src/ark/types/platform/webhook_create_params.py b/src/ark/types/platform/webhook_create_params.py new file mode 100644 index 0000000..574222f --- /dev/null +++ b/src/ark/types/platform/webhook_create_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["WebhookCreateParams"] + + +class WebhookCreateParams(TypedDict, total=False): + name: Required[str] + """Display name for the webhook""" + + url: Required[str] + """Webhook endpoint URL (must be HTTPS)""" + + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + """Events to subscribe to. Empty array means all events.""" diff --git a/src/ark/types/platform/webhook_create_response.py b/src/ark/types/platform/webhook_create_response.py new file mode 100644 index 0000000..37ca2ca --- /dev/null +++ b/src/ark/types/platform/webhook_create_response.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookCreateResponse", "Data"] + + +class Data(BaseModel): + id: str + """Platform webhook ID""" + + created_at: datetime = FieldInfo(alias="createdAt") + + enabled: bool + """Whether the webhook is active""" + + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + """Subscribed events (empty = all events)""" + + name: str + """Webhook name for identification""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + + url: str + """Webhook endpoint URL""" + + +class WebhookCreateResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/webhook_delete_response.py b/src/ark/types/platform/webhook_delete_response.py similarity index 81% rename from src/ark/types/webhook_delete_response.py rename to src/ark/types/platform/webhook_delete_response.py index 63e9ff8..aee22ba 100644 --- a/src/ark/types/webhook_delete_response.py +++ b/src/ark/types/platform/webhook_delete_response.py @@ -2,8 +2,8 @@ from typing_extensions import Literal -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookDeleteResponse", "Data"] diff --git a/src/ark/types/platform/webhook_list_deliveries_params.py b/src/ark/types/platform/webhook_list_deliveries_params.py new file mode 100644 index 0000000..3d00ef0 --- /dev/null +++ b/src/ark/types/platform/webhook_list_deliveries_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["WebhookListDeliveriesParams"] + + +class WebhookListDeliveriesParams(TypedDict, total=False): + after: int + """Only deliveries after this Unix timestamp""" + + before: int + """Only deliveries before this Unix timestamp""" + + event: Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + """Filter by event type""" + + page: int + """Page number (default 1)""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Items per page (default 30, max 100)""" + + success: bool + """Filter by delivery success""" + + tenant_id: Annotated[str, PropertyInfo(alias="tenantId")] + """Filter by tenant ID""" + + webhook_id: Annotated[str, PropertyInfo(alias="webhookId")] + """Filter by platform webhook ID""" diff --git a/src/ark/types/platform/webhook_list_deliveries_response.py b/src/ark/types/platform/webhook_list_deliveries_response.py new file mode 100644 index 0000000..25160c7 --- /dev/null +++ b/src/ark/types/platform/webhook_list_deliveries_response.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["WebhookListDeliveriesResponse"] + + +class WebhookListDeliveriesResponse(BaseModel): + """Summary of a platform webhook delivery attempt""" + + id: str + """Unique delivery ID""" + + attempt: int + """Attempt number (1 for first attempt, higher for retries)""" + + event: Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + """Event type""" + + status_code: Optional[int] = FieldInfo(alias="statusCode", default=None) + """HTTP status code returned by your endpoint (null if connection failed)""" + + success: bool + """Whether delivery was successful (2xx response)""" + + tenant_id: str = FieldInfo(alias="tenantId") + """Tenant that triggered the event""" + + timestamp: datetime + """When the delivery was attempted""" + + url: str + """Endpoint URL the delivery was sent to""" + + webhook_id: str = FieldInfo(alias="webhookId") + """Platform webhook ID""" + + will_retry: bool = FieldInfo(alias="willRetry") + """Whether this delivery will be retried""" diff --git a/src/ark/types/platform/webhook_list_response.py b/src/ark/types/platform/webhook_list_response.py new file mode 100644 index 0000000..3afef57 --- /dev/null +++ b/src/ark/types/platform/webhook_list_response.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookListResponse", "Data"] + + +class Data(BaseModel): + id: str + """Platform webhook ID""" + + created_at: datetime = FieldInfo(alias="createdAt") + + enabled: bool + + events: List[str] + + name: str + + url: str + + +class WebhookListResponse(BaseModel): + data: List[Data] + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/platform/webhook_replay_delivery_response.py b/src/ark/types/platform/webhook_replay_delivery_response.py new file mode 100644 index 0000000..311bef0 --- /dev/null +++ b/src/ark/types/platform/webhook_replay_delivery_response.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookReplayDeliveryResponse", "Data"] + + +class Data(BaseModel): + duration: int + """Request duration in milliseconds""" + + new_delivery_id: str = FieldInfo(alias="newDeliveryId") + """ID of the new delivery created by the replay""" + + original_delivery_id: str = FieldInfo(alias="originalDeliveryId") + """ID of the original delivery that was replayed""" + + status_code: Optional[int] = FieldInfo(alias="statusCode", default=None) + """HTTP status code from your endpoint""" + + success: bool + """Whether the replay was successful""" + + timestamp: datetime + """When the replay was executed""" + + +class WebhookReplayDeliveryResponse(BaseModel): + """Result of replaying a platform webhook delivery""" + + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/platform/webhook_retrieve_delivery_response.py b/src/ark/types/platform/webhook_retrieve_delivery_response.py new file mode 100644 index 0000000..6da891a --- /dev/null +++ b/src/ark/types/platform/webhook_retrieve_delivery_response.py @@ -0,0 +1,81 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookRetrieveDeliveryResponse", "Data", "DataRequest", "DataResponse"] + + +class DataRequest(BaseModel): + """Request details""" + + headers: Optional[Dict[str, str]] = None + """Request headers including signature""" + + payload: Optional[Dict[str, object]] = None + """The complete webhook payload that was sent""" + + +class DataResponse(BaseModel): + """Response details""" + + body: Optional[str] = None + """Response body (truncated if too large)""" + + duration: Optional[int] = None + """Response time in milliseconds""" + + +class Data(BaseModel): + id: str + """Unique delivery ID""" + + attempt: int + """Attempt number""" + + event: str + """Event type""" + + request: DataRequest + """Request details""" + + response: DataResponse + """Response details""" + + status_code: Optional[int] = FieldInfo(alias="statusCode", default=None) + """HTTP status code from your endpoint""" + + success: bool + """Whether delivery was successful""" + + tenant_id: str = FieldInfo(alias="tenantId") + """Tenant that triggered the event""" + + timestamp: datetime + """When delivery was attempted""" + + url: str + """Endpoint URL""" + + webhook_id: str = FieldInfo(alias="webhookId") + """Platform webhook ID""" + + webhook_name: str = FieldInfo(alias="webhookName") + """Platform webhook name""" + + will_retry: bool = FieldInfo(alias="willRetry") + """Whether this will be retried""" + + +class WebhookRetrieveDeliveryResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/platform/webhook_retrieve_response.py b/src/ark/types/platform/webhook_retrieve_response.py new file mode 100644 index 0000000..b589458 --- /dev/null +++ b/src/ark/types/platform/webhook_retrieve_response.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookRetrieveResponse", "Data"] + + +class Data(BaseModel): + id: str + """Platform webhook ID""" + + created_at: datetime = FieldInfo(alias="createdAt") + + enabled: bool + """Whether the webhook is active""" + + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + """Subscribed events (empty = all events)""" + + name: str + """Webhook name for identification""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + + url: str + """Webhook endpoint URL""" + + +class WebhookRetrieveResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/webhook_test_params.py b/src/ark/types/platform/webhook_test_params.py similarity index 100% rename from src/ark/types/webhook_test_params.py rename to src/ark/types/platform/webhook_test_params.py diff --git a/src/ark/types/platform/webhook_test_response.py b/src/ark/types/platform/webhook_test_response.py new file mode 100644 index 0000000..56036e1 --- /dev/null +++ b/src/ark/types/platform/webhook_test_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookTestResponse", "Data"] + + +class Data(BaseModel): + duration_ms: int = FieldInfo(alias="durationMs") + """Request duration in milliseconds""" + + status_code: int = FieldInfo(alias="statusCode") + """HTTP status code from the webhook endpoint""" + + success: bool + """Whether the webhook endpoint responded with a 2xx status""" + + error: Optional[str] = None + """Error message if the request failed""" + + +class WebhookTestResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/platform/webhook_update_params.py b/src/ark/types/platform/webhook_update_params.py new file mode 100644 index 0000000..6da78ab --- /dev/null +++ b/src/ark/types/platform/webhook_update_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal, TypedDict + +__all__ = ["WebhookUpdateParams"] + + +class WebhookUpdateParams(TypedDict, total=False): + enabled: bool + """Enable or disable the webhook""" + + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + """Events to subscribe to. Empty array means all events.""" + + name: str + """Display name for the webhook""" + + url: str + """Webhook endpoint URL (must be HTTPS)""" diff --git a/src/ark/types/platform/webhook_update_response.py b/src/ark/types/platform/webhook_update_response.py new file mode 100644 index 0000000..d300235 --- /dev/null +++ b/src/ark/types/platform/webhook_update_response.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookUpdateResponse", "Data"] + + +class Data(BaseModel): + id: str + """Platform webhook ID""" + + created_at: datetime = FieldInfo(alias="createdAt") + + enabled: bool + """Whether the webhook is active""" + + events: List[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + """Subscribed events (empty = all events)""" + + name: str + """Webhook name for identification""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + + url: str + """Webhook endpoint URL""" + + +class WebhookUpdateResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/suppression_bulk_create_params.py b/src/ark/types/suppression_bulk_create_params.py deleted file mode 100644 index d0d0754..0000000 --- a/src/ark/types/suppression_bulk_create_params.py +++ /dev/null @@ -1,19 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Iterable, Optional -from typing_extensions import Required, TypedDict - -__all__ = ["SuppressionBulkCreateParams", "Suppression"] - - -class SuppressionBulkCreateParams(TypedDict, total=False): - suppressions: Required[Iterable[Suppression]] - - -class Suppression(TypedDict, total=False): - address: Required[str] - - reason: Optional[str] - """Reason for suppression (accepts null)""" diff --git a/src/ark/types/suppression_bulk_create_response.py b/src/ark/types/suppression_bulk_create_response.py deleted file mode 100644 index 4545f32..0000000 --- a/src/ark/types/suppression_bulk_create_response.py +++ /dev/null @@ -1,32 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .shared.api_meta import APIMeta - -__all__ = ["SuppressionBulkCreateResponse", "Data"] - - -class Data(BaseModel): - added: int - """Newly suppressed addresses""" - - failed: int - """Invalid addresses skipped""" - - total_requested: int = FieldInfo(alias="totalRequested") - """Total addresses in request""" - - updated: int - """Already suppressed addresses (updated reason)""" - - -class SuppressionBulkCreateResponse(BaseModel): - data: Data - - meta: APIMeta - - success: Literal[True] diff --git a/src/ark/types/tenant.py b/src/ark/types/tenant.py index 5c1591a..1140756 100644 --- a/src/ark/types/tenant.py +++ b/src/ark/types/tenant.py @@ -4,6 +4,8 @@ from datetime import datetime from typing_extensions import Literal +from pydantic import Field as FieldInfo + from .._models import BaseModel __all__ = ["Tenant"] @@ -13,7 +15,7 @@ class Tenant(BaseModel): id: str """Unique identifier for the tenant""" - created_at: datetime + created_at: datetime = FieldInfo(alias="createdAt") """When the tenant was created""" metadata: Dict[str, Union[str, float, bool, None]] @@ -30,5 +32,5 @@ class Tenant(BaseModel): - `archived` - Soft-deleted """ - updated_at: datetime + updated_at: datetime = FieldInfo(alias="updatedAt") """When the tenant was last updated""" diff --git a/src/ark/types/tenant_usage_item.py b/src/ark/types/tenant_usage_item.py new file mode 100644 index 0000000..d303755 --- /dev/null +++ b/src/ark/types/tenant_usage_item.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel +from .email_rates import EmailRates +from .email_counts import EmailCounts + +__all__ = ["TenantUsageItem"] + + +class TenantUsageItem(BaseModel): + """Usage record for a single tenant (camelCase for SDK)""" + + emails: EmailCounts + """Email delivery counts""" + + rates: EmailRates + """Email delivery rates (as decimals, e.g., 0.95 = 95%)""" + + status: Literal["active", "suspended", "archived"] + """Current tenant status""" + + tenant_id: str = FieldInfo(alias="tenantId") + """Unique tenant identifier""" + + tenant_name: str = FieldInfo(alias="tenantName") + """Tenant display name""" + + external_id: Optional[str] = FieldInfo(alias="externalId", default=None) + """Your external ID for this tenant""" diff --git a/src/ark/types/tenants/__init__.py b/src/ark/types/tenants/__init__.py new file mode 100644 index 0000000..c52a153 --- /dev/null +++ b/src/ark/types/tenants/__init__.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .dns_record import DNSRecord as DNSRecord +from .tenant_usage import TenantUsage as TenantUsage +from .track_domain import TrackDomain as TrackDomain +from .webhook_test_params import WebhookTestParams as WebhookTestParams +from .domain_create_params import DomainCreateParams as DomainCreateParams +from .domain_list_response import DomainListResponse as DomainListResponse +from .usage_retrieve_params import UsageRetrieveParams as UsageRetrieveParams +from .webhook_create_params import WebhookCreateParams as WebhookCreateParams +from .webhook_list_response import WebhookListResponse as WebhookListResponse +from .webhook_test_response import WebhookTestResponse as WebhookTestResponse +from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams +from .credential_list_params import CredentialListParams as CredentialListParams +from .domain_create_response import DomainCreateResponse as DomainCreateResponse +from .domain_delete_response import DomainDeleteResponse as DomainDeleteResponse +from .domain_verify_response import DomainVerifyResponse as DomainVerifyResponse +from .tracking_create_params import TrackingCreateParams as TrackingCreateParams +from .tracking_list_response import TrackingListResponse as TrackingListResponse +from .tracking_update_params import TrackingUpdateParams as TrackingUpdateParams +from .suppression_list_params import SuppressionListParams as SuppressionListParams +from .tenant_usage_timeseries import TenantUsageTimeseries as TenantUsageTimeseries +from .usage_retrieve_response import UsageRetrieveResponse as UsageRetrieveResponse +from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse +from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse +from .webhook_update_response import WebhookUpdateResponse as WebhookUpdateResponse +from .credential_create_params import CredentialCreateParams as CredentialCreateParams +from .credential_list_response import CredentialListResponse as CredentialListResponse +from .credential_update_params import CredentialUpdateParams as CredentialUpdateParams +from .domain_retrieve_response import DomainRetrieveResponse as DomainRetrieveResponse +from .tracking_create_response import TrackingCreateResponse as TrackingCreateResponse +from .tracking_delete_response import TrackingDeleteResponse as TrackingDeleteResponse +from .tracking_update_response import TrackingUpdateResponse as TrackingUpdateResponse +from .tracking_verify_response import TrackingVerifyResponse as TrackingVerifyResponse +from .suppression_create_params import SuppressionCreateParams as SuppressionCreateParams +from .suppression_list_response import SuppressionListResponse as SuppressionListResponse +from .webhook_retrieve_response import WebhookRetrieveResponse as WebhookRetrieveResponse +from .credential_create_response import CredentialCreateResponse as CredentialCreateResponse +from .credential_delete_response import CredentialDeleteResponse as CredentialDeleteResponse +from .credential_retrieve_params import CredentialRetrieveParams as CredentialRetrieveParams +from .credential_update_response import CredentialUpdateResponse as CredentialUpdateResponse +from .tracking_retrieve_response import TrackingRetrieveResponse as TrackingRetrieveResponse +from .suppression_create_response import SuppressionCreateResponse as SuppressionCreateResponse +from .suppression_delete_response import SuppressionDeleteResponse as SuppressionDeleteResponse +from .credential_retrieve_response import CredentialRetrieveResponse as CredentialRetrieveResponse +from .suppression_retrieve_response import SuppressionRetrieveResponse as SuppressionRetrieveResponse +from .webhook_list_deliveries_params import WebhookListDeliveriesParams as WebhookListDeliveriesParams +from .usage_retrieve_timeseries_params import UsageRetrieveTimeseriesParams as UsageRetrieveTimeseriesParams +from .webhook_list_deliveries_response import WebhookListDeliveriesResponse as WebhookListDeliveriesResponse +from .webhook_replay_delivery_response import WebhookReplayDeliveryResponse as WebhookReplayDeliveryResponse +from .usage_retrieve_timeseries_response import UsageRetrieveTimeseriesResponse as UsageRetrieveTimeseriesResponse +from .webhook_retrieve_delivery_response import WebhookRetrieveDeliveryResponse as WebhookRetrieveDeliveryResponse diff --git a/src/ark/types/tenants/credential_create_params.py b/src/ark/types/tenants/credential_create_params.py new file mode 100644 index 0000000..3bbcc2d --- /dev/null +++ b/src/ark/types/tenants/credential_create_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["CredentialCreateParams"] + + +class CredentialCreateParams(TypedDict, total=False): + name: Required[str] + """Name for the credential. + + Can only contain letters, numbers, hyphens, and underscores. Max 50 characters. + """ + + type: Required[Literal["smtp", "api"]] + """Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + """ diff --git a/src/ark/types/tenants/credential_create_response.py b/src/ark/types/tenants/credential_create_response.py new file mode 100644 index 0000000..685e534 --- /dev/null +++ b/src/ark/types/tenants/credential_create_response.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["CredentialCreateResponse", "Data"] + + +class Data(BaseModel): + id: int + """Unique identifier for the credential""" + + created_at: datetime = FieldInfo(alias="createdAt") + """When the credential was created""" + + hold: bool + """ + Whether the credential is on hold (disabled). When `true`, the credential cannot + be used to send emails. + """ + + key: str + """The credential key (secret). + + **Store this securely** - it will not be shown again unless you use the reveal + parameter. + """ + + last_used_at: Optional[datetime] = FieldInfo(alias="lastUsedAt", default=None) + """When the credential was last used to send an email""" + + name: str + """Name of the credential""" + + type: Literal["smtp", "api"] + """Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + """ + + updated_at: datetime = FieldInfo(alias="updatedAt") + """When the credential was last updated""" + + smtp_username: Optional[str] = FieldInfo(alias="smtpUsername", default=None) + """SMTP username for authentication. + + Only included for SMTP credentials. Format: `{tenantId}/{key}` + """ + + +class CredentialCreateResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenants/credential_delete_response.py b/src/ark/types/tenants/credential_delete_response.py new file mode 100644 index 0000000..772b55e --- /dev/null +++ b/src/ark/types/tenants/credential_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["CredentialDeleteResponse", "Data"] + + +class Data(BaseModel): + deleted: Literal[True] + + +class CredentialDeleteResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenants/credential_list_params.py b/src/ark/types/tenants/credential_list_params.py new file mode 100644 index 0000000..29245af --- /dev/null +++ b/src/ark/types/tenants/credential_list_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["CredentialListParams"] + + +class CredentialListParams(TypedDict, total=False): + page: int + """Page number (1-indexed)""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Number of items per page (max 100)""" + + type: Literal["smtp", "api"] + """Filter by credential type""" diff --git a/src/ark/types/tenants/credential_list_response.py b/src/ark/types/tenants/credential_list_response.py new file mode 100644 index 0000000..f69e705 --- /dev/null +++ b/src/ark/types/tenants/credential_list_response.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["CredentialListResponse"] + + +class CredentialListResponse(BaseModel): + id: int + """Unique identifier for the credential""" + + created_at: datetime = FieldInfo(alias="createdAt") + """When the credential was created""" + + hold: bool + """ + Whether the credential is on hold (disabled). When `true`, the credential cannot + be used to send emails. + """ + + last_used_at: Optional[datetime] = FieldInfo(alias="lastUsedAt", default=None) + """When the credential was last used to send an email""" + + name: str + """Name of the credential""" + + type: Literal["smtp", "api"] + """Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + """ + + updated_at: datetime = FieldInfo(alias="updatedAt") + """When the credential was last updated""" diff --git a/src/ark/types/tenants/credential_retrieve_params.py b/src/ark/types/tenants/credential_retrieve_params.py new file mode 100644 index 0000000..90e046f --- /dev/null +++ b/src/ark/types/tenants/credential_retrieve_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["CredentialRetrieveParams"] + + +class CredentialRetrieveParams(TypedDict, total=False): + tenant_id: Required[Annotated[str, PropertyInfo(alias="tenantId")]] + + reveal: bool + """Set to `true` to include the credential key in the response""" diff --git a/src/ark/types/tenants/credential_retrieve_response.py b/src/ark/types/tenants/credential_retrieve_response.py new file mode 100644 index 0000000..a3633e8 --- /dev/null +++ b/src/ark/types/tenants/credential_retrieve_response.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["CredentialRetrieveResponse", "Data"] + + +class Data(BaseModel): + id: int + """Unique identifier for the credential""" + + created_at: datetime = FieldInfo(alias="createdAt") + """When the credential was created""" + + hold: bool + """ + Whether the credential is on hold (disabled). When `true`, the credential cannot + be used to send emails. + """ + + last_used_at: Optional[datetime] = FieldInfo(alias="lastUsedAt", default=None) + """When the credential was last used to send an email""" + + name: str + """Name of the credential""" + + type: Literal["smtp", "api"] + """Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + """ + + updated_at: datetime = FieldInfo(alias="updatedAt") + """When the credential was last updated""" + + key: Optional[str] = None + """The credential key (secret). Only included when: + + - Creating a new credential (always returned) + - Retrieving with `reveal=true` + """ + + smtp_username: Optional[str] = FieldInfo(alias="smtpUsername", default=None) + """SMTP username for authentication. + + Only included for SMTP credentials when the key is revealed. + """ + + +class CredentialRetrieveResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenants/credential_update_params.py b/src/ark/types/tenants/credential_update_params.py new file mode 100644 index 0000000..f96a32c --- /dev/null +++ b/src/ark/types/tenants/credential_update_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["CredentialUpdateParams"] + + +class CredentialUpdateParams(TypedDict, total=False): + tenant_id: Required[Annotated[str, PropertyInfo(alias="tenantId")]] + + hold: bool + """ + Set to `true` to disable the credential (put on hold). Set to `false` to enable + the credential (release from hold). + """ + + name: str + """New name for the credential""" diff --git a/src/ark/types/tenants/credential_update_response.py b/src/ark/types/tenants/credential_update_response.py new file mode 100644 index 0000000..9b19b2f --- /dev/null +++ b/src/ark/types/tenants/credential_update_response.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["CredentialUpdateResponse", "Data"] + + +class Data(BaseModel): + id: int + """Unique identifier for the credential""" + + created_at: datetime = FieldInfo(alias="createdAt") + """When the credential was created""" + + hold: bool + """ + Whether the credential is on hold (disabled). When `true`, the credential cannot + be used to send emails. + """ + + last_used_at: Optional[datetime] = FieldInfo(alias="lastUsedAt", default=None) + """When the credential was last used to send an email""" + + name: str + """Name of the credential""" + + type: Literal["smtp", "api"] + """Type of credential: + + - `smtp` - For SMTP-based email sending + - `api` - For API-based email sending + """ + + updated_at: datetime = FieldInfo(alias="updatedAt") + """When the credential was last updated""" + + key: Optional[str] = None + """The credential key (secret). Only included when: + + - Creating a new credential (always returned) + - Retrieving with `reveal=true` + """ + + smtp_username: Optional[str] = FieldInfo(alias="smtpUsername", default=None) + """SMTP username for authentication. + + Only included for SMTP credentials when the key is revealed. + """ + + +class CredentialUpdateResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/dns_record.py b/src/ark/types/tenants/dns_record.py similarity index 98% rename from src/ark/types/dns_record.py rename to src/ark/types/tenants/dns_record.py index 775e76e..50d47af 100644 --- a/src/ark/types/dns_record.py +++ b/src/ark/types/tenants/dns_record.py @@ -5,7 +5,7 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel __all__ = ["DNSRecord"] diff --git a/src/ark/types/domain_create_params.py b/src/ark/types/tenants/domain_create_params.py similarity index 100% rename from src/ark/types/domain_create_params.py rename to src/ark/types/tenants/domain_create_params.py diff --git a/src/ark/types/domain_create_response.py b/src/ark/types/tenants/domain_create_response.py similarity index 94% rename from src/ark/types/domain_create_response.py rename to src/ark/types/tenants/domain_create_response.py index 56fdef4..39a6dcd 100644 --- a/src/ark/types/domain_create_response.py +++ b/src/ark/types/tenants/domain_create_response.py @@ -6,9 +6,9 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel from .dns_record import DNSRecord -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["DomainCreateResponse", "Data", "DataDNSRecords"] @@ -124,6 +124,12 @@ class Data(BaseModel): Domain must be verified before sending emails. """ + tenant_id: Optional[str] = None + """ID of the tenant this domain belongs to""" + + tenant_name: Optional[str] = None + """Name of the tenant this domain belongs to""" + verified_at: Optional[datetime] = FieldInfo(alias="verifiedAt", default=None) """Timestamp when the domain ownership was verified, or null if not yet verified""" diff --git a/src/ark/types/domain_delete_response.py b/src/ark/types/tenants/domain_delete_response.py similarity index 81% rename from src/ark/types/domain_delete_response.py rename to src/ark/types/tenants/domain_delete_response.py index 71f6734..ded7cb7 100644 --- a/src/ark/types/domain_delete_response.py +++ b/src/ark/types/tenants/domain_delete_response.py @@ -2,8 +2,8 @@ from typing_extensions import Literal -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["DomainDeleteResponse", "Data"] diff --git a/src/ark/types/domain_list_response.py b/src/ark/types/tenants/domain_list_response.py similarity index 63% rename from src/ark/types/domain_list_response.py rename to src/ark/types/tenants/domain_list_response.py index 50edc86..b7f328f 100644 --- a/src/ark/types/domain_list_response.py +++ b/src/ark/types/tenants/domain_list_response.py @@ -1,10 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from typing_extensions import Literal -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["DomainListResponse", "Data", "DataDomain"] @@ -22,6 +22,14 @@ class DataDomain(BaseModel): Domain must be verified before sending emails. """ + tenant_id: Optional[str] = None + """ID of the tenant this domain belongs to (included when filtering by tenant_id)""" + + tenant_name: Optional[str] = None + """ + Name of the tenant this domain belongs to (included when filtering by tenant_id) + """ + class Data(BaseModel): domains: List[DataDomain] diff --git a/src/ark/types/domain_retrieve_response.py b/src/ark/types/tenants/domain_retrieve_response.py similarity index 94% rename from src/ark/types/domain_retrieve_response.py rename to src/ark/types/tenants/domain_retrieve_response.py index bc9d550..6bb15d2 100644 --- a/src/ark/types/domain_retrieve_response.py +++ b/src/ark/types/tenants/domain_retrieve_response.py @@ -6,9 +6,9 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel from .dns_record import DNSRecord -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["DomainRetrieveResponse", "Data", "DataDNSRecords"] @@ -124,6 +124,12 @@ class Data(BaseModel): Domain must be verified before sending emails. """ + tenant_id: Optional[str] = None + """ID of the tenant this domain belongs to""" + + tenant_name: Optional[str] = None + """Name of the tenant this domain belongs to""" + verified_at: Optional[datetime] = FieldInfo(alias="verifiedAt", default=None) """Timestamp when the domain ownership was verified, or null if not yet verified""" diff --git a/src/ark/types/domain_verify_response.py b/src/ark/types/tenants/domain_verify_response.py similarity index 94% rename from src/ark/types/domain_verify_response.py rename to src/ark/types/tenants/domain_verify_response.py index 8bc1ae9..cadeb40 100644 --- a/src/ark/types/domain_verify_response.py +++ b/src/ark/types/tenants/domain_verify_response.py @@ -6,9 +6,9 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel from .dns_record import DNSRecord -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["DomainVerifyResponse", "Data", "DataDNSRecords"] @@ -124,6 +124,12 @@ class Data(BaseModel): Domain must be verified before sending emails. """ + tenant_id: Optional[str] = None + """ID of the tenant this domain belongs to""" + + tenant_name: Optional[str] = None + """Name of the tenant this domain belongs to""" + verified_at: Optional[datetime] = FieldInfo(alias="verifiedAt", default=None) """Timestamp when the domain ownership was verified, or null if not yet verified""" diff --git a/src/ark/types/suppression_create_params.py b/src/ark/types/tenants/suppression_create_params.py similarity index 100% rename from src/ark/types/suppression_create_params.py rename to src/ark/types/tenants/suppression_create_params.py diff --git a/src/ark/types/suppression_create_response.py b/src/ark/types/tenants/suppression_create_response.py similarity index 89% rename from src/ark/types/suppression_create_response.py rename to src/ark/types/tenants/suppression_create_response.py index 11b0897..021a7e8 100644 --- a/src/ark/types/suppression_create_response.py +++ b/src/ark/types/tenants/suppression_create_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["SuppressionCreateResponse", "Data"] diff --git a/src/ark/types/suppression_delete_response.py b/src/ark/types/tenants/suppression_delete_response.py similarity index 82% rename from src/ark/types/suppression_delete_response.py rename to src/ark/types/tenants/suppression_delete_response.py index 31e4ccc..a942543 100644 --- a/src/ark/types/suppression_delete_response.py +++ b/src/ark/types/tenants/suppression_delete_response.py @@ -2,8 +2,8 @@ from typing_extensions import Literal -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["SuppressionDeleteResponse", "Data"] diff --git a/src/ark/types/suppression_list_params.py b/src/ark/types/tenants/suppression_list_params.py similarity index 90% rename from src/ark/types/suppression_list_params.py rename to src/ark/types/tenants/suppression_list_params.py index 71ee20d..57fa8bf 100644 --- a/src/ark/types/suppression_list_params.py +++ b/src/ark/types/tenants/suppression_list_params.py @@ -4,7 +4,7 @@ from typing_extensions import Annotated, TypedDict -from .._utils import PropertyInfo +from ..._utils import PropertyInfo __all__ = ["SuppressionListParams"] diff --git a/src/ark/types/suppression_list_response.py b/src/ark/types/tenants/suppression_list_response.py similarity index 92% rename from src/ark/types/suppression_list_response.py rename to src/ark/types/tenants/suppression_list_response.py index bf55aa5..7a67cee 100644 --- a/src/ark/types/suppression_list_response.py +++ b/src/ark/types/tenants/suppression_list_response.py @@ -5,7 +5,7 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel __all__ = ["SuppressionListResponse"] diff --git a/src/ark/types/suppression_retrieve_response.py b/src/ark/types/tenants/suppression_retrieve_response.py similarity index 91% rename from src/ark/types/suppression_retrieve_response.py rename to src/ark/types/tenants/suppression_retrieve_response.py index f20757a..699da56 100644 --- a/src/ark/types/suppression_retrieve_response.py +++ b/src/ark/types/tenants/suppression_retrieve_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["SuppressionRetrieveResponse", "Data"] diff --git a/src/ark/types/tenants/tenant_usage.py b/src/ark/types/tenants/tenant_usage.py new file mode 100644 index 0000000..98101fe --- /dev/null +++ b/src/ark/types/tenants/tenant_usage.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from ..email_rates import EmailRates +from ..email_counts import EmailCounts +from ..usage_period import UsagePeriod + +__all__ = ["TenantUsage"] + + +class TenantUsage(BaseModel): + """Tenant usage statistics""" + + emails: EmailCounts + """Email delivery counts""" + + period: UsagePeriod + """Time period for usage data""" + + rates: EmailRates + """Email delivery rates (as decimals, e.g., 0.95 = 95%)""" + + tenant_id: str + """Unique tenant identifier""" + + tenant_name: str + """Tenant display name""" + + external_id: Optional[str] = None + """Your external ID for this tenant (from metadata)""" diff --git a/src/ark/types/tenants/tenant_usage_timeseries.py b/src/ark/types/tenants/tenant_usage_timeseries.py new file mode 100644 index 0000000..cfc970d --- /dev/null +++ b/src/ark/types/tenants/tenant_usage_timeseries.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel +from ..usage_period import UsagePeriod + +__all__ = ["TenantUsageTimeseries", "Data"] + + +class Data(BaseModel): + """Single timeseries data point""" + + bounced: int + """Bounces in this bucket""" + + delivered: int + """Emails delivered in this bucket""" + + hard_failed: int + """Hard failures in this bucket""" + + held: int + """Emails held in this bucket""" + + sent: int + """Emails sent in this bucket""" + + soft_failed: int + """Soft failures in this bucket""" + + timestamp: datetime + """Start of time bucket""" + + +class TenantUsageTimeseries(BaseModel): + """Timeseries usage statistics""" + + data: List[Data] + """Array of time-bucketed data points""" + + granularity: Literal["hour", "day", "week", "month"] + """Time bucket granularity""" + + period: UsagePeriod + """Time period for usage data""" + + tenant_id: str + """Unique tenant identifier""" + + tenant_name: str + """Tenant display name""" diff --git a/src/ark/types/track_domain.py b/src/ark/types/tenants/track_domain.py similarity index 98% rename from src/ark/types/track_domain.py rename to src/ark/types/tenants/track_domain.py index f2786ea..104d261 100644 --- a/src/ark/types/track_domain.py +++ b/src/ark/types/tenants/track_domain.py @@ -6,7 +6,7 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel __all__ = ["TrackDomain", "DNSRecord"] diff --git a/src/ark/types/tracking_create_params.py b/src/ark/types/tenants/tracking_create_params.py similarity index 96% rename from src/ark/types/tracking_create_params.py rename to src/ark/types/tenants/tracking_create_params.py index d647b65..1ad766b 100644 --- a/src/ark/types/tracking_create_params.py +++ b/src/ark/types/tenants/tracking_create_params.py @@ -5,7 +5,7 @@ from typing import Optional from typing_extensions import Required, Annotated, TypedDict -from .._utils import PropertyInfo +from ..._utils import PropertyInfo __all__ = ["TrackingCreateParams"] diff --git a/src/ark/types/tracking_create_response.py b/src/ark/types/tenants/tracking_create_response.py similarity index 81% rename from src/ark/types/tracking_create_response.py rename to src/ark/types/tenants/tracking_create_response.py index 93cbf43..879b574 100644 --- a/src/ark/types/tracking_create_response.py +++ b/src/ark/types/tenants/tracking_create_response.py @@ -2,9 +2,9 @@ from typing_extensions import Literal -from .._models import BaseModel +from ..._models import BaseModel from .track_domain import TrackDomain -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["TrackingCreateResponse"] diff --git a/src/ark/types/tracking_delete_response.py b/src/ark/types/tenants/tracking_delete_response.py similarity index 81% rename from src/ark/types/tracking_delete_response.py rename to src/ark/types/tenants/tracking_delete_response.py index bd440c9..3a1243e 100644 --- a/src/ark/types/tracking_delete_response.py +++ b/src/ark/types/tenants/tracking_delete_response.py @@ -2,8 +2,8 @@ from typing_extensions import Literal -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["TrackingDeleteResponse", "Data"] diff --git a/src/ark/types/tracking_list_response.py b/src/ark/types/tenants/tracking_list_response.py similarity index 86% rename from src/ark/types/tracking_list_response.py rename to src/ark/types/tenants/tracking_list_response.py index ffd31a8..53bddb7 100644 --- a/src/ark/types/tracking_list_response.py +++ b/src/ark/types/tenants/tracking_list_response.py @@ -5,9 +5,9 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel +from ..._models import BaseModel from .track_domain import TrackDomain -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["TrackingListResponse", "Data"] diff --git a/src/ark/types/tracking_retrieve_response.py b/src/ark/types/tenants/tracking_retrieve_response.py similarity index 81% rename from src/ark/types/tracking_retrieve_response.py rename to src/ark/types/tenants/tracking_retrieve_response.py index cc974a2..7e39ca9 100644 --- a/src/ark/types/tracking_retrieve_response.py +++ b/src/ark/types/tenants/tracking_retrieve_response.py @@ -2,9 +2,9 @@ from typing_extensions import Literal -from .._models import BaseModel +from ..._models import BaseModel from .track_domain import TrackDomain -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["TrackingRetrieveResponse"] diff --git a/src/ark/types/tracking_update_params.py b/src/ark/types/tenants/tracking_update_params.py similarity index 83% rename from src/ark/types/tracking_update_params.py rename to src/ark/types/tenants/tracking_update_params.py index 3ad46a0..febe059 100644 --- a/src/ark/types/tracking_update_params.py +++ b/src/ark/types/tenants/tracking_update_params.py @@ -3,14 +3,16 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Annotated, TypedDict +from typing_extensions import Required, Annotated, TypedDict -from .._utils import PropertyInfo +from ..._utils import PropertyInfo __all__ = ["TrackingUpdateParams"] class TrackingUpdateParams(TypedDict, total=False): + tenant_id: Required[Annotated[str, PropertyInfo(alias="tenantId")]] + excluded_click_domains: Annotated[Optional[str], PropertyInfo(alias="excludedClickDomains")] """Comma-separated list of domains to exclude from click tracking (accepts null)""" diff --git a/src/ark/types/tracking_update_response.py b/src/ark/types/tenants/tracking_update_response.py similarity index 81% rename from src/ark/types/tracking_update_response.py rename to src/ark/types/tenants/tracking_update_response.py index a1761e5..c06efee 100644 --- a/src/ark/types/tracking_update_response.py +++ b/src/ark/types/tenants/tracking_update_response.py @@ -2,9 +2,9 @@ from typing_extensions import Literal -from .._models import BaseModel +from ..._models import BaseModel from .track_domain import TrackDomain -from .shared.api_meta import APIMeta +from ..shared.api_meta import APIMeta __all__ = ["TrackingUpdateResponse"] diff --git a/src/ark/types/tracking_verify_response.py b/src/ark/types/tenants/tracking_verify_response.py similarity index 94% rename from src/ark/types/tracking_verify_response.py rename to src/ark/types/tenants/tracking_verify_response.py index 076afbf..d1f2df9 100644 --- a/src/ark/types/tracking_verify_response.py +++ b/src/ark/types/tenants/tracking_verify_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["TrackingVerifyResponse", "Data", "DataDNSRecord"] diff --git a/src/ark/types/tenants/usage_retrieve_params.py b/src/ark/types/tenants/usage_retrieve_params.py new file mode 100644 index 0000000..48c3de3 --- /dev/null +++ b/src/ark/types/tenants/usage_retrieve_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["UsageRetrieveParams"] + + +class UsageRetrieveParams(TypedDict, total=False): + period: str + """Time period for usage data. Defaults to current month. + + **Formats:** + + - Shortcuts: `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + - Month: `2024-01` + - Range: `2024-01-01..2024-01-31` + - Day: `2024-01-15` + """ + + timezone: str + """Timezone for period calculations (IANA format). Defaults to UTC.""" diff --git a/src/ark/types/tenants/usage_retrieve_response.py b/src/ark/types/tenants/usage_retrieve_response.py new file mode 100644 index 0000000..d7abac0 --- /dev/null +++ b/src/ark/types/tenants/usage_retrieve_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from .tenant_usage import TenantUsage +from ..shared.api_meta import APIMeta + +__all__ = ["UsageRetrieveResponse"] + + +class UsageRetrieveResponse(BaseModel): + """Usage statistics for a single tenant""" + + data: TenantUsage + """Tenant usage statistics""" + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenants/usage_retrieve_timeseries_params.py b/src/ark/types/tenants/usage_retrieve_timeseries_params.py new file mode 100644 index 0000000..c6682b3 --- /dev/null +++ b/src/ark/types/tenants/usage_retrieve_timeseries_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["UsageRetrieveTimeseriesParams"] + + +class UsageRetrieveTimeseriesParams(TypedDict, total=False): + granularity: Literal["hour", "day", "week", "month"] + """Time bucket size for data points""" + + period: str + """Time period for timeseries data. Defaults to current month.""" + + timezone: str + """Timezone for period calculations (IANA format). Defaults to UTC.""" diff --git a/src/ark/types/tenants/usage_retrieve_timeseries_response.py b/src/ark/types/tenants/usage_retrieve_timeseries_response.py new file mode 100644 index 0000000..74962fb --- /dev/null +++ b/src/ark/types/tenants/usage_retrieve_timeseries_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta +from .tenant_usage_timeseries import TenantUsageTimeseries + +__all__ = ["UsageRetrieveTimeseriesResponse"] + + +class UsageRetrieveTimeseriesResponse(BaseModel): + """Timeseries usage data for a tenant""" + + data: TenantUsageTimeseries + """Timeseries usage statistics""" + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/webhook_create_params.py b/src/ark/types/tenants/webhook_create_params.py similarity index 97% rename from src/ark/types/webhook_create_params.py rename to src/ark/types/tenants/webhook_create_params.py index 9899826..aa4767e 100644 --- a/src/ark/types/webhook_create_params.py +++ b/src/ark/types/tenants/webhook_create_params.py @@ -5,7 +5,7 @@ from typing import List, Optional from typing_extensions import Literal, Required, Annotated, TypedDict -from .._utils import PropertyInfo +from ..._utils import PropertyInfo __all__ = ["WebhookCreateParams"] diff --git a/src/ark/types/webhook_create_response.py b/src/ark/types/tenants/webhook_create_response.py similarity index 93% rename from src/ark/types/webhook_create_response.py rename to src/ark/types/tenants/webhook_create_response.py index d78bb58..b30b19e 100644 --- a/src/ark/types/webhook_create_response.py +++ b/src/ark/types/tenants/webhook_create_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookCreateResponse", "Data"] diff --git a/src/ark/types/tenants/webhook_delete_response.py b/src/ark/types/tenants/webhook_delete_response.py new file mode 100644 index 0000000..aee22ba --- /dev/null +++ b/src/ark/types/tenants/webhook_delete_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel +from ..shared.api_meta import APIMeta + +__all__ = ["WebhookDeleteResponse", "Data"] + + +class Data(BaseModel): + message: str + + +class WebhookDeleteResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/webhook_list_deliveries_params.py b/src/ark/types/tenants/webhook_list_deliveries_params.py similarity index 83% rename from src/ark/types/webhook_list_deliveries_params.py rename to src/ark/types/tenants/webhook_list_deliveries_params.py index c4048e4..c990a0c 100644 --- a/src/ark/types/webhook_list_deliveries_params.py +++ b/src/ark/types/tenants/webhook_list_deliveries_params.py @@ -2,14 +2,16 @@ from __future__ import annotations -from typing_extensions import Literal, Annotated, TypedDict +from typing_extensions import Literal, Required, Annotated, TypedDict -from .._utils import PropertyInfo +from ..._utils import PropertyInfo __all__ = ["WebhookListDeliveriesParams"] class WebhookListDeliveriesParams(TypedDict, total=False): + tenant_id: Required[Annotated[str, PropertyInfo(alias="tenantId")]] + after: int """Only deliveries after this Unix timestamp""" diff --git a/src/ark/types/webhook_list_deliveries_response.py b/src/ark/types/tenants/webhook_list_deliveries_response.py similarity index 96% rename from src/ark/types/webhook_list_deliveries_response.py rename to src/ark/types/tenants/webhook_list_deliveries_response.py index 71a5fe2..ce0b50d 100644 --- a/src/ark/types/webhook_list_deliveries_response.py +++ b/src/ark/types/tenants/webhook_list_deliveries_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookListDeliveriesResponse", "Data"] diff --git a/src/ark/types/webhook_list_response.py b/src/ark/types/tenants/webhook_list_response.py similarity index 87% rename from src/ark/types/webhook_list_response.py rename to src/ark/types/tenants/webhook_list_response.py index 10acdd6..ccd413a 100644 --- a/src/ark/types/webhook_list_response.py +++ b/src/ark/types/tenants/webhook_list_response.py @@ -3,8 +3,8 @@ from typing import List from typing_extensions import Literal -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookListResponse", "Data", "DataWebhook"] diff --git a/src/ark/types/webhook_replay_delivery_response.py b/src/ark/types/tenants/webhook_replay_delivery_response.py similarity index 93% rename from src/ark/types/webhook_replay_delivery_response.py rename to src/ark/types/tenants/webhook_replay_delivery_response.py index f3961fd..58c9db6 100644 --- a/src/ark/types/webhook_replay_delivery_response.py +++ b/src/ark/types/tenants/webhook_replay_delivery_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookReplayDeliveryResponse", "Data"] diff --git a/src/ark/types/webhook_retrieve_delivery_response.py b/src/ark/types/tenants/webhook_retrieve_delivery_response.py similarity index 97% rename from src/ark/types/webhook_retrieve_delivery_response.py rename to src/ark/types/tenants/webhook_retrieve_delivery_response.py index efec5c4..f13d5f0 100644 --- a/src/ark/types/webhook_retrieve_delivery_response.py +++ b/src/ark/types/tenants/webhook_retrieve_delivery_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookRetrieveDeliveryResponse", "Data", "DataRequest", "DataResponse"] diff --git a/src/ark/types/webhook_retrieve_response.py b/src/ark/types/tenants/webhook_retrieve_response.py similarity index 93% rename from src/ark/types/webhook_retrieve_response.py rename to src/ark/types/tenants/webhook_retrieve_response.py index 6cb2f2e..d2a9d91 100644 --- a/src/ark/types/webhook_retrieve_response.py +++ b/src/ark/types/tenants/webhook_retrieve_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookRetrieveResponse", "Data"] diff --git a/src/ark/types/tenants/webhook_test_params.py b/src/ark/types/tenants/webhook_test_params.py new file mode 100644 index 0000000..dc43485 --- /dev/null +++ b/src/ark/types/tenants/webhook_test_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["WebhookTestParams"] + + +class WebhookTestParams(TypedDict, total=False): + tenant_id: Required[Annotated[str, PropertyInfo(alias="tenantId")]] + + event: Required[ + Literal[ + "MessageSent", + "MessageDelayed", + "MessageDeliveryFailed", + "MessageHeld", + "MessageBounced", + "MessageLinkClicked", + "MessageLoaded", + "DomainDNSError", + ] + ] + """Event type to simulate""" diff --git a/src/ark/types/webhook_test_response.py b/src/ark/types/tenants/webhook_test_response.py similarity index 92% rename from src/ark/types/webhook_test_response.py rename to src/ark/types/tenants/webhook_test_response.py index a51041b..60da1c2 100644 --- a/src/ark/types/webhook_test_response.py +++ b/src/ark/types/tenants/webhook_test_response.py @@ -5,8 +5,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookTestResponse", "Data"] diff --git a/src/ark/types/webhook_update_params.py b/src/ark/types/tenants/webhook_update_params.py similarity index 67% rename from src/ark/types/webhook_update_params.py rename to src/ark/types/tenants/webhook_update_params.py index 934b579..79587f2 100644 --- a/src/ark/types/webhook_update_params.py +++ b/src/ark/types/tenants/webhook_update_params.py @@ -3,15 +3,17 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Annotated, TypedDict +from typing_extensions import Required, Annotated, TypedDict -from .._types import SequenceNotStr -from .._utils import PropertyInfo +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo __all__ = ["WebhookUpdateParams"] class WebhookUpdateParams(TypedDict, total=False): + tenant_id: Required[Annotated[str, PropertyInfo(alias="tenantId")]] + all_events: Annotated[Optional[bool], PropertyInfo(alias="allEvents")] enabled: Optional[bool] diff --git a/src/ark/types/webhook_update_response.py b/src/ark/types/tenants/webhook_update_response.py similarity index 93% rename from src/ark/types/webhook_update_response.py rename to src/ark/types/tenants/webhook_update_response.py index 4d9a0c6..d98f7d4 100644 --- a/src/ark/types/webhook_update_response.py +++ b/src/ark/types/tenants/webhook_update_response.py @@ -6,8 +6,8 @@ from pydantic import Field as FieldInfo -from .._models import BaseModel -from .shared.api_meta import APIMeta +from ..._models import BaseModel +from ..shared.api_meta import APIMeta __all__ = ["WebhookUpdateResponse", "Data"] diff --git a/src/ark/types/usage_export_params.py b/src/ark/types/usage_export_params.py new file mode 100644 index 0000000..2d02b6e --- /dev/null +++ b/src/ark/types/usage_export_params.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["UsageExportParams"] + + +class UsageExportParams(TypedDict, total=False): + format: Literal["csv", "jsonl"] + """Export format""" + + min_sent: Annotated[int, PropertyInfo(alias="minSent")] + """Only include tenants with at least this many emails sent""" + + period: str + """Time period for export. + + **Shortcuts:** `this_month`, `last_month`, `last_30_days`, etc. + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + """ + + status: Literal["active", "suspended", "archived"] + """Filter by tenant status""" + + timezone: str + """Timezone for period calculations (IANA format)""" diff --git a/src/ark/types/usage_export_response.py b/src/ark/types/usage_export_response.py new file mode 100644 index 0000000..3680f66 --- /dev/null +++ b/src/ark/types/usage_export_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal, TypeAlias + +from .._models import BaseModel + +__all__ = ["UsageExportResponse", "UsageExportResponseItem"] + + +class UsageExportResponseItem(BaseModel): + """Single row in usage export (JSON format)""" + + bounce_rate: float + """Bounce rate (0-1)""" + + bounced: int + """Emails that bounced""" + + delivered: int + """Emails successfully delivered""" + + delivery_rate: float + """Delivery rate (0-1)""" + + hard_failed: int + """Emails that hard-failed""" + + held: int + """Emails currently held""" + + sent: int + """Total emails sent""" + + soft_failed: int + """Emails that soft-failed""" + + status: Literal["active", "suspended", "archived"] + """Current tenant status""" + + tenant_id: str + """Unique tenant identifier""" + + tenant_name: str + """Tenant display name""" + + external_id: Optional[str] = None + """Your external ID for this tenant""" + + +UsageExportResponse: TypeAlias = List[UsageExportResponseItem] diff --git a/src/ark/types/usage_list_tenants_params.py b/src/ark/types/usage_list_tenants_params.py new file mode 100644 index 0000000..bf4d702 --- /dev/null +++ b/src/ark/types/usage_list_tenants_params.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["UsageListTenantsParams"] + + +class UsageListTenantsParams(TypedDict, total=False): + min_sent: Annotated[int, PropertyInfo(alias="minSent")] + """Only include tenants with at least this many emails sent""" + + page: int + """Page number (1-indexed)""" + + period: str + """Time period for usage data. Defaults to current month. + + **Shortcuts:** `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + """ + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Results per page (max 100)""" + + sort: Literal[ + "sent", + "-sent", + "delivered", + "-delivered", + "bounce_rate", + "-bounce_rate", + "delivery_rate", + "-delivery_rate", + "tenant_name", + "-tenant_name", + ] + """Sort order for results. Prefix with `-` for descending order.""" + + status: Literal["active", "suspended", "archived"] + """Filter by tenant status""" + + timezone: str + """Timezone for period calculations (IANA format). Defaults to UTC.""" diff --git a/src/ark/types/usage_period.py b/src/ark/types/usage_period.py new file mode 100644 index 0000000..4372295 --- /dev/null +++ b/src/ark/types/usage_period.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["UsagePeriod"] + + +class UsagePeriod(BaseModel): + """Time period for usage data""" + + end: datetime + """Period end (inclusive)""" + + start: datetime + """Period start (inclusive)""" diff --git a/src/ark/types/usage_retrieve_params.py b/src/ark/types/usage_retrieve_params.py new file mode 100644 index 0000000..89a75ba --- /dev/null +++ b/src/ark/types/usage_retrieve_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["UsageRetrieveParams"] + + +class UsageRetrieveParams(TypedDict, total=False): + period: str + """Time period for usage data. + + **Shortcuts:** `today`, `yesterday`, `this_week`, `last_week`, `this_month`, + `last_month`, `last_7_days`, `last_30_days`, `last_90_days` + + **Month format:** `2024-01` (YYYY-MM) + + **Custom range:** `2024-01-01..2024-01-15` + """ + + timezone: str + """Timezone for period calculations (IANA format)""" diff --git a/tests/api_resources/platform/__init__.py b/tests/api_resources/platform/__init__.py new file mode 100644 index 0000000..fd8019a --- /dev/null +++ b/tests/api_resources/platform/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/platform/test_webhooks.py similarity index 68% rename from tests/api_resources/test_webhooks.py rename to tests/api_resources/platform/test_webhooks.py index e994815..b64b0a6 100644 --- a/tests/api_resources/test_webhooks.py +++ b/tests/api_resources/platform/test_webhooks.py @@ -8,7 +8,9 @@ import pytest from ark import Ark, AsyncArk -from ark.types import ( +from tests.utils import assert_matches_type +from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from ark.types.platform import ( WebhookListResponse, WebhookTestResponse, WebhookCreateResponse, @@ -19,7 +21,6 @@ WebhookReplayDeliveryResponse, WebhookRetrieveDeliveryResponse, ) -from tests.utils import assert_matches_type base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -29,28 +30,26 @@ class TestWebhooks: @parametrize def test_method_create(self, client: Ark) -> None: - webhook = client.webhooks.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", + webhook = client.platform.webhooks.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", ) assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Ark) -> None: - webhook = client.webhooks.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", - all_events=True, - enabled=True, + webhook = client.platform.webhooks.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", events=["MessageSent", "MessageDeliveryFailed", "MessageBounced"], ) assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) @parametrize def test_raw_response_create(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", + response = client.platform.webhooks.with_raw_response.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", ) assert response.is_closed is True @@ -60,9 +59,9 @@ def test_raw_response_create(self, client: Ark) -> None: @parametrize def test_streaming_response_create(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", + with client.platform.webhooks.with_streaming_response.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -74,15 +73,15 @@ def test_streaming_response_create(self, client: Ark) -> None: @parametrize def test_method_retrieve(self, client: Ark) -> None: - webhook = client.webhooks.retrieve( - "webhookId", + webhook = client.platform.webhooks.retrieve( + "pwh_abc123def456", ) assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.retrieve( - "webhookId", + response = client.platform.webhooks.with_raw_response.retrieve( + "pwh_abc123def456", ) assert response.is_closed is True @@ -92,8 +91,8 @@ def test_raw_response_retrieve(self, client: Ark) -> None: @parametrize def test_streaming_response_retrieve(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.retrieve( - "webhookId", + with client.platform.webhooks.with_streaming_response.retrieve( + "pwh_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -106,33 +105,32 @@ def test_streaming_response_retrieve(self, client: Ark) -> None: @parametrize def test_path_params_retrieve(self, client: Ark) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.retrieve( + client.platform.webhooks.with_raw_response.retrieve( "", ) @parametrize def test_method_update(self, client: Ark) -> None: - webhook = client.webhooks.update( - webhook_id="webhookId", + webhook = client.platform.webhooks.update( + webhook_id="pwh_abc123def456", ) assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Ark) -> None: - webhook = client.webhooks.update( - webhook_id="webhookId", - all_events=True, + webhook = client.platform.webhooks.update( + webhook_id="pwh_abc123def456", enabled=True, - events=["string"], - name="name", + events=["MessageSent"], + name="x", url="https://example.com", ) assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) @parametrize def test_raw_response_update(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.update( - webhook_id="webhookId", + response = client.platform.webhooks.with_raw_response.update( + webhook_id="pwh_abc123def456", ) assert response.is_closed is True @@ -142,8 +140,8 @@ def test_raw_response_update(self, client: Ark) -> None: @parametrize def test_streaming_response_update(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.update( - webhook_id="webhookId", + with client.platform.webhooks.with_streaming_response.update( + webhook_id="pwh_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -156,18 +154,18 @@ def test_streaming_response_update(self, client: Ark) -> None: @parametrize def test_path_params_update(self, client: Ark) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.update( + client.platform.webhooks.with_raw_response.update( webhook_id="", ) @parametrize def test_method_list(self, client: Ark) -> None: - webhook = client.webhooks.list() + webhook = client.platform.webhooks.list() assert_matches_type(WebhookListResponse, webhook, path=["response"]) @parametrize def test_raw_response_list(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.list() + response = client.platform.webhooks.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -176,7 +174,7 @@ def test_raw_response_list(self, client: Ark) -> None: @parametrize def test_streaming_response_list(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.list() as response: + with client.platform.webhooks.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -187,15 +185,15 @@ def test_streaming_response_list(self, client: Ark) -> None: @parametrize def test_method_delete(self, client: Ark) -> None: - webhook = client.webhooks.delete( - "webhookId", + webhook = client.platform.webhooks.delete( + "pwh_abc123def456", ) assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) @parametrize def test_raw_response_delete(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.delete( - "webhookId", + response = client.platform.webhooks.with_raw_response.delete( + "pwh_abc123def456", ) assert response.is_closed is True @@ -205,8 +203,8 @@ def test_raw_response_delete(self, client: Ark) -> None: @parametrize def test_streaming_response_delete(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.delete( - "webhookId", + with client.platform.webhooks.with_streaming_response.delete( + "pwh_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -219,74 +217,60 @@ def test_streaming_response_delete(self, client: Ark) -> None: @parametrize def test_path_params_delete(self, client: Ark) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.delete( + client.platform.webhooks.with_raw_response.delete( "", ) @parametrize def test_method_list_deliveries(self, client: Ark) -> None: - webhook = client.webhooks.list_deliveries( - webhook_id="webhookId", - ) - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + webhook = client.platform.webhooks.list_deliveries() + assert_matches_type(SyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) @parametrize def test_method_list_deliveries_with_all_params(self, client: Ark) -> None: - webhook = client.webhooks.list_deliveries( - webhook_id="webhookId", + webhook = client.platform.webhooks.list_deliveries( after=0, before=0, event="MessageSent", - page=1, - per_page=1, + page=0, + per_page=100, success=True, + tenant_id="tenantId", + webhook_id="webhookId", ) - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + assert_matches_type(SyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) @parametrize def test_raw_response_list_deliveries(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.list_deliveries( - webhook_id="webhookId", - ) + response = client.platform.webhooks.with_raw_response.list_deliveries() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" webhook = response.parse() - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + assert_matches_type(SyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) @parametrize def test_streaming_response_list_deliveries(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.list_deliveries( - webhook_id="webhookId", - ) as response: + with client.platform.webhooks.with_streaming_response.list_deliveries() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" webhook = response.parse() - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + assert_matches_type(SyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) assert cast(Any, response.is_closed) is True - @parametrize - def test_path_params_list_deliveries(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.list_deliveries( - webhook_id="", - ) - @parametrize def test_method_replay_delivery(self, client: Ark) -> None: - webhook = client.webhooks.replay_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + webhook = client.platform.webhooks.replay_delivery( + "pwd_abc123def456", ) assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) @parametrize def test_raw_response_replay_delivery(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.replay_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + response = client.platform.webhooks.with_raw_response.replay_delivery( + "pwd_abc123def456", ) assert response.is_closed is True @@ -296,9 +280,8 @@ def test_raw_response_replay_delivery(self, client: Ark) -> None: @parametrize def test_streaming_response_replay_delivery(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.replay_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + with client.platform.webhooks.with_streaming_response.replay_delivery( + "pwd_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -310,31 +293,22 @@ def test_streaming_response_replay_delivery(self, client: Ark) -> None: @parametrize def test_path_params_replay_delivery(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.replay_delivery( - delivery_id="deliveryId", - webhook_id="", - ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): - client.webhooks.with_raw_response.replay_delivery( - delivery_id="", - webhook_id="webhookId", + client.platform.webhooks.with_raw_response.replay_delivery( + "", ) @parametrize def test_method_retrieve_delivery(self, client: Ark) -> None: - webhook = client.webhooks.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + webhook = client.platform.webhooks.retrieve_delivery( + "pwd_abc123def456", ) assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) @parametrize def test_raw_response_retrieve_delivery(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + response = client.platform.webhooks.with_raw_response.retrieve_delivery( + "pwd_abc123def456", ) assert response.is_closed is True @@ -344,9 +318,8 @@ def test_raw_response_retrieve_delivery(self, client: Ark) -> None: @parametrize def test_streaming_response_retrieve_delivery(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + with client.platform.webhooks.with_streaming_response.retrieve_delivery( + "pwd_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -358,30 +331,23 @@ def test_streaming_response_retrieve_delivery(self, client: Ark) -> None: @parametrize def test_path_params_retrieve_delivery(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="", - ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): - client.webhooks.with_raw_response.retrieve_delivery( - delivery_id="", - webhook_id="webhookId", + client.platform.webhooks.with_raw_response.retrieve_delivery( + "", ) @parametrize def test_method_test(self, client: Ark) -> None: - webhook = client.webhooks.test( - webhook_id="webhookId", + webhook = client.platform.webhooks.test( + webhook_id="pwh_abc123def456", event="MessageSent", ) assert_matches_type(WebhookTestResponse, webhook, path=["response"]) @parametrize def test_raw_response_test(self, client: Ark) -> None: - response = client.webhooks.with_raw_response.test( - webhook_id="webhookId", + response = client.platform.webhooks.with_raw_response.test( + webhook_id="pwh_abc123def456", event="MessageSent", ) @@ -392,8 +358,8 @@ def test_raw_response_test(self, client: Ark) -> None: @parametrize def test_streaming_response_test(self, client: Ark) -> None: - with client.webhooks.with_streaming_response.test( - webhook_id="webhookId", + with client.platform.webhooks.with_streaming_response.test( + webhook_id="pwh_abc123def456", event="MessageSent", ) as response: assert not response.is_closed @@ -407,7 +373,7 @@ def test_streaming_response_test(self, client: Ark) -> None: @parametrize def test_path_params_test(self, client: Ark) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - client.webhooks.with_raw_response.test( + client.platform.webhooks.with_raw_response.test( webhook_id="", event="MessageSent", ) @@ -420,28 +386,26 @@ class TestAsyncWebhooks: @parametrize async def test_method_create(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", + webhook = await async_client.platform.webhooks.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", ) assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", - all_events=True, - enabled=True, + webhook = await async_client.platform.webhooks.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", events=["MessageSent", "MessageDeliveryFailed", "MessageBounced"], ) assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", + response = await async_client.platform.webhooks.with_raw_response.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", ) assert response.is_closed is True @@ -451,9 +415,9 @@ async def test_raw_response_create(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.create( - name="My App Webhook", - url="https://myapp.com/webhooks/email", + async with async_client.platform.webhooks.with_streaming_response.create( + name="Central Event Processor", + url="https://myplatform.com/webhooks/email-events", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -465,15 +429,15 @@ async def test_streaming_response_create(self, async_client: AsyncArk) -> None: @parametrize async def test_method_retrieve(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.retrieve( - "webhookId", + webhook = await async_client.platform.webhooks.retrieve( + "pwh_abc123def456", ) assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.retrieve( - "webhookId", + response = await async_client.platform.webhooks.with_raw_response.retrieve( + "pwh_abc123def456", ) assert response.is_closed is True @@ -483,8 +447,8 @@ async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.retrieve( - "webhookId", + async with async_client.platform.webhooks.with_streaming_response.retrieve( + "pwh_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -497,33 +461,32 @@ async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None @parametrize async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.retrieve( + await async_client.platform.webhooks.with_raw_response.retrieve( "", ) @parametrize async def test_method_update(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.update( - webhook_id="webhookId", + webhook = await async_client.platform.webhooks.update( + webhook_id="pwh_abc123def456", ) assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.update( - webhook_id="webhookId", - all_events=True, + webhook = await async_client.platform.webhooks.update( + webhook_id="pwh_abc123def456", enabled=True, - events=["string"], - name="name", + events=["MessageSent"], + name="x", url="https://example.com", ) assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.update( - webhook_id="webhookId", + response = await async_client.platform.webhooks.with_raw_response.update( + webhook_id="pwh_abc123def456", ) assert response.is_closed is True @@ -533,8 +496,8 @@ async def test_raw_response_update(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.update( - webhook_id="webhookId", + async with async_client.platform.webhooks.with_streaming_response.update( + webhook_id="pwh_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -547,18 +510,18 @@ async def test_streaming_response_update(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_update(self, async_client: AsyncArk) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.update( + await async_client.platform.webhooks.with_raw_response.update( webhook_id="", ) @parametrize async def test_method_list(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.list() + webhook = await async_client.platform.webhooks.list() assert_matches_type(WebhookListResponse, webhook, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.list() + response = await async_client.platform.webhooks.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -567,7 +530,7 @@ async def test_raw_response_list(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.list() as response: + async with async_client.platform.webhooks.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -578,15 +541,15 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None: @parametrize async def test_method_delete(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.delete( - "webhookId", + webhook = await async_client.platform.webhooks.delete( + "pwh_abc123def456", ) assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.delete( - "webhookId", + response = await async_client.platform.webhooks.with_raw_response.delete( + "pwh_abc123def456", ) assert response.is_closed is True @@ -596,8 +559,8 @@ async def test_raw_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.delete( - "webhookId", + async with async_client.platform.webhooks.with_streaming_response.delete( + "pwh_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -610,74 +573,60 @@ async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_delete(self, async_client: AsyncArk) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.delete( + await async_client.platform.webhooks.with_raw_response.delete( "", ) @parametrize async def test_method_list_deliveries(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.list_deliveries( - webhook_id="webhookId", - ) - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + webhook = await async_client.platform.webhooks.list_deliveries() + assert_matches_type(AsyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) @parametrize async def test_method_list_deliveries_with_all_params(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.list_deliveries( - webhook_id="webhookId", + webhook = await async_client.platform.webhooks.list_deliveries( after=0, before=0, event="MessageSent", - page=1, - per_page=1, + page=0, + per_page=100, success=True, + tenant_id="tenantId", + webhook_id="webhookId", ) - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + assert_matches_type(AsyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) @parametrize async def test_raw_response_list_deliveries(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.list_deliveries( - webhook_id="webhookId", - ) + response = await async_client.platform.webhooks.with_raw_response.list_deliveries() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" webhook = await response.parse() - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + assert_matches_type(AsyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) @parametrize async def test_streaming_response_list_deliveries(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.list_deliveries( - webhook_id="webhookId", - ) as response: + async with async_client.platform.webhooks.with_streaming_response.list_deliveries() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" webhook = await response.parse() - assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + assert_matches_type(AsyncPageNumberPagination[WebhookListDeliveriesResponse], webhook, path=["response"]) assert cast(Any, response.is_closed) is True - @parametrize - async def test_path_params_list_deliveries(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.list_deliveries( - webhook_id="", - ) - @parametrize async def test_method_replay_delivery(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.replay_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + webhook = await async_client.platform.webhooks.replay_delivery( + "pwd_abc123def456", ) assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) @parametrize async def test_raw_response_replay_delivery(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.replay_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + response = await async_client.platform.webhooks.with_raw_response.replay_delivery( + "pwd_abc123def456", ) assert response.is_closed is True @@ -687,9 +636,8 @@ async def test_raw_response_replay_delivery(self, async_client: AsyncArk) -> Non @parametrize async def test_streaming_response_replay_delivery(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.replay_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + async with async_client.platform.webhooks.with_streaming_response.replay_delivery( + "pwd_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -701,31 +649,22 @@ async def test_streaming_response_replay_delivery(self, async_client: AsyncArk) @parametrize async def test_path_params_replay_delivery(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.replay_delivery( - delivery_id="deliveryId", - webhook_id="", - ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): - await async_client.webhooks.with_raw_response.replay_delivery( - delivery_id="", - webhook_id="webhookId", + await async_client.platform.webhooks.with_raw_response.replay_delivery( + "", ) @parametrize async def test_method_retrieve_delivery(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + webhook = await async_client.platform.webhooks.retrieve_delivery( + "pwd_abc123def456", ) assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) @parametrize async def test_raw_response_retrieve_delivery(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + response = await async_client.platform.webhooks.with_raw_response.retrieve_delivery( + "pwd_abc123def456", ) assert response.is_closed is True @@ -735,9 +674,8 @@ async def test_raw_response_retrieve_delivery(self, async_client: AsyncArk) -> N @parametrize async def test_streaming_response_retrieve_delivery(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="webhookId", + async with async_client.platform.webhooks.with_streaming_response.retrieve_delivery( + "pwd_abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -749,30 +687,23 @@ async def test_streaming_response_retrieve_delivery(self, async_client: AsyncArk @parametrize async def test_path_params_retrieve_delivery(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.retrieve_delivery( - delivery_id="deliveryId", - webhook_id="", - ) - with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): - await async_client.webhooks.with_raw_response.retrieve_delivery( - delivery_id="", - webhook_id="webhookId", + await async_client.platform.webhooks.with_raw_response.retrieve_delivery( + "", ) @parametrize async def test_method_test(self, async_client: AsyncArk) -> None: - webhook = await async_client.webhooks.test( - webhook_id="webhookId", + webhook = await async_client.platform.webhooks.test( + webhook_id="pwh_abc123def456", event="MessageSent", ) assert_matches_type(WebhookTestResponse, webhook, path=["response"]) @parametrize async def test_raw_response_test(self, async_client: AsyncArk) -> None: - response = await async_client.webhooks.with_raw_response.test( - webhook_id="webhookId", + response = await async_client.platform.webhooks.with_raw_response.test( + webhook_id="pwh_abc123def456", event="MessageSent", ) @@ -783,8 +714,8 @@ async def test_raw_response_test(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_test(self, async_client: AsyncArk) -> None: - async with async_client.webhooks.with_streaming_response.test( - webhook_id="webhookId", + async with async_client.platform.webhooks.with_streaming_response.test( + webhook_id="pwh_abc123def456", event="MessageSent", ) as response: assert not response.is_closed @@ -798,7 +729,7 @@ async def test_streaming_response_test(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_test(self, async_client: AsyncArk) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): - await async_client.webhooks.with_raw_response.test( + await async_client.platform.webhooks.with_raw_response.test( webhook_id="", event="MessageSent", ) diff --git a/tests/api_resources/tenants/__init__.py b/tests/api_resources/tenants/__init__.py new file mode 100644 index 0000000..fd8019a --- /dev/null +++ b/tests/api_resources/tenants/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/tenants/test_credentials.py b/tests/api_resources/tenants/test_credentials.py new file mode 100644 index 0000000..b919a73 --- /dev/null +++ b/tests/api_resources/tenants/test_credentials.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from ark import Ark, AsyncArk +from tests.utils import assert_matches_type +from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from ark.types.tenants import ( + CredentialListResponse, + CredentialCreateResponse, + CredentialDeleteResponse, + CredentialUpdateResponse, + CredentialRetrieveResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestCredentials: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Ark) -> None: + credential = client.tenants.credentials.create( + tenant_id="cm6abc123def456", + name="production-smtp", + type="smtp", + ) + assert_matches_type(CredentialCreateResponse, credential, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Ark) -> None: + response = client.tenants.credentials.with_raw_response.create( + tenant_id="cm6abc123def456", + name="production-smtp", + type="smtp", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = response.parse() + assert_matches_type(CredentialCreateResponse, credential, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Ark) -> None: + with client.tenants.credentials.with_streaming_response.create( + tenant_id="cm6abc123def456", + name="production-smtp", + type="smtp", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = response.parse() + assert_matches_type(CredentialCreateResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.credentials.with_raw_response.create( + tenant_id="", + name="production-smtp", + type="smtp", + ) + + @parametrize + def test_method_retrieve(self, client: Ark) -> None: + credential = client.tenants.credentials.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + ) + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Ark) -> None: + credential = client.tenants.credentials.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + reveal=True, + ) + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Ark) -> None: + response = client.tenants.credentials.with_raw_response.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = response.parse() + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Ark) -> None: + with client.tenants.credentials.with_streaming_response.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = response.parse() + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.credentials.with_raw_response.retrieve( + credential_id=123, + tenant_id="", + ) + + @parametrize + def test_method_update(self, client: Ark) -> None: + credential = client.tenants.credentials.update( + credential_id=123, + tenant_id="cm6abc123def456", + ) + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Ark) -> None: + credential = client.tenants.credentials.update( + credential_id=123, + tenant_id="cm6abc123def456", + hold=True, + name="production-smtp-v2", + ) + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Ark) -> None: + response = client.tenants.credentials.with_raw_response.update( + credential_id=123, + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = response.parse() + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Ark) -> None: + with client.tenants.credentials.with_streaming_response.update( + credential_id=123, + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = response.parse() + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.credentials.with_raw_response.update( + credential_id=123, + tenant_id="", + ) + + @parametrize + def test_method_list(self, client: Ark) -> None: + credential = client.tenants.credentials.list( + tenant_id="cm6abc123def456", + ) + assert_matches_type(SyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Ark) -> None: + credential = client.tenants.credentials.list( + tenant_id="cm6abc123def456", + page=1, + per_page=1, + type="smtp", + ) + assert_matches_type(SyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Ark) -> None: + response = client.tenants.credentials.with_raw_response.list( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = response.parse() + assert_matches_type(SyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Ark) -> None: + with client.tenants.credentials.with_streaming_response.list( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = response.parse() + assert_matches_type(SyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.credentials.with_raw_response.list( + tenant_id="", + ) + + @parametrize + def test_method_delete(self, client: Ark) -> None: + credential = client.tenants.credentials.delete( + credential_id=123, + tenant_id="cm6abc123def456", + ) + assert_matches_type(CredentialDeleteResponse, credential, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Ark) -> None: + response = client.tenants.credentials.with_raw_response.delete( + credential_id=123, + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = response.parse() + assert_matches_type(CredentialDeleteResponse, credential, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Ark) -> None: + with client.tenants.credentials.with_streaming_response.delete( + credential_id=123, + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = response.parse() + assert_matches_type(CredentialDeleteResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.credentials.with_raw_response.delete( + credential_id=123, + tenant_id="", + ) + + +class TestAsyncCredentials: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.create( + tenant_id="cm6abc123def456", + name="production-smtp", + type="smtp", + ) + assert_matches_type(CredentialCreateResponse, credential, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.credentials.with_raw_response.create( + tenant_id="cm6abc123def456", + name="production-smtp", + type="smtp", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = await response.parse() + assert_matches_type(CredentialCreateResponse, credential, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncArk) -> None: + async with async_client.tenants.credentials.with_streaming_response.create( + tenant_id="cm6abc123def456", + name="production-smtp", + type="smtp", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = await response.parse() + assert_matches_type(CredentialCreateResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.credentials.with_raw_response.create( + tenant_id="", + name="production-smtp", + type="smtp", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + ) + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + reveal=True, + ) + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.credentials.with_raw_response.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = await response.parse() + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: + async with async_client.tenants.credentials.with_streaming_response.retrieve( + credential_id=123, + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = await response.parse() + assert_matches_type(CredentialRetrieveResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.credentials.with_raw_response.retrieve( + credential_id=123, + tenant_id="", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.update( + credential_id=123, + tenant_id="cm6abc123def456", + ) + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.update( + credential_id=123, + tenant_id="cm6abc123def456", + hold=True, + name="production-smtp-v2", + ) + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.credentials.with_raw_response.update( + credential_id=123, + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = await response.parse() + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncArk) -> None: + async with async_client.tenants.credentials.with_streaming_response.update( + credential_id=123, + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = await response.parse() + assert_matches_type(CredentialUpdateResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.credentials.with_raw_response.update( + credential_id=123, + tenant_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.list( + tenant_id="cm6abc123def456", + ) + assert_matches_type(AsyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.list( + tenant_id="cm6abc123def456", + page=1, + per_page=1, + type="smtp", + ) + assert_matches_type(AsyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.credentials.with_raw_response.list( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = await response.parse() + assert_matches_type(AsyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncArk) -> None: + async with async_client.tenants.credentials.with_streaming_response.list( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = await response.parse() + assert_matches_type(AsyncPageNumberPagination[CredentialListResponse], credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.credentials.with_raw_response.list( + tenant_id="", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncArk) -> None: + credential = await async_client.tenants.credentials.delete( + credential_id=123, + tenant_id="cm6abc123def456", + ) + assert_matches_type(CredentialDeleteResponse, credential, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.credentials.with_raw_response.delete( + credential_id=123, + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + credential = await response.parse() + assert_matches_type(CredentialDeleteResponse, credential, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: + async with async_client.tenants.credentials.with_streaming_response.delete( + credential_id=123, + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + credential = await response.parse() + assert_matches_type(CredentialDeleteResponse, credential, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.credentials.with_raw_response.delete( + credential_id=123, + tenant_id="", + ) diff --git a/tests/api_resources/test_domains.py b/tests/api_resources/tenants/test_domains.py similarity index 60% rename from tests/api_resources/test_domains.py rename to tests/api_resources/tenants/test_domains.py index 41dcfff..b6d9162 100644 --- a/tests/api_resources/test_domains.py +++ b/tests/api_resources/tenants/test_domains.py @@ -8,14 +8,14 @@ import pytest from ark import Ark, AsyncArk -from ark.types import ( +from tests.utils import assert_matches_type +from ark.types.tenants import ( DomainListResponse, DomainCreateResponse, DomainDeleteResponse, DomainVerifyResponse, DomainRetrieveResponse, ) -from tests.utils import assert_matches_type base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -25,14 +25,16 @@ class TestDomains: @parametrize def test_method_create(self, client: Ark) -> None: - domain = client.domains.create( + domain = client.tenants.domains.create( + tenant_id="cm6abc123def456", name="notifications.myapp.com", ) assert_matches_type(DomainCreateResponse, domain, path=["response"]) @parametrize def test_raw_response_create(self, client: Ark) -> None: - response = client.domains.with_raw_response.create( + response = client.tenants.domains.with_raw_response.create( + tenant_id="cm6abc123def456", name="notifications.myapp.com", ) @@ -43,7 +45,8 @@ def test_raw_response_create(self, client: Ark) -> None: @parametrize def test_streaming_response_create(self, client: Ark) -> None: - with client.domains.with_streaming_response.create( + with client.tenants.domains.with_streaming_response.create( + tenant_id="cm6abc123def456", name="notifications.myapp.com", ) as response: assert not response.is_closed @@ -54,17 +57,27 @@ def test_streaming_response_create(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_create(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.domains.with_raw_response.create( + tenant_id="", + name="notifications.myapp.com", + ) + @parametrize def test_method_retrieve(self, client: Ark) -> None: - domain = client.domains.retrieve( - "domainId", + domain = client.tenants.domains.retrieve( + domain_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(DomainRetrieveResponse, domain, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Ark) -> None: - response = client.domains.with_raw_response.retrieve( - "domainId", + response = client.tenants.domains.with_raw_response.retrieve( + domain_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -74,8 +87,9 @@ def test_raw_response_retrieve(self, client: Ark) -> None: @parametrize def test_streaming_response_retrieve(self, client: Ark) -> None: - with client.domains.with_streaming_response.retrieve( - "domainId", + with client.tenants.domains.with_streaming_response.retrieve( + domain_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -87,19 +101,30 @@ def test_streaming_response_retrieve(self, client: Ark) -> None: @parametrize def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.domains.with_raw_response.retrieve( + domain_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `domain_id` but received ''"): - client.domains.with_raw_response.retrieve( - "", + client.tenants.domains.with_raw_response.retrieve( + domain_id="", + tenant_id="cm6abc123def456", ) @parametrize def test_method_list(self, client: Ark) -> None: - domain = client.domains.list() + domain = client.tenants.domains.list( + "cm6abc123def456", + ) assert_matches_type(DomainListResponse, domain, path=["response"]) @parametrize def test_raw_response_list(self, client: Ark) -> None: - response = client.domains.with_raw_response.list() + response = client.tenants.domains.with_raw_response.list( + "cm6abc123def456", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -108,7 +133,9 @@ def test_raw_response_list(self, client: Ark) -> None: @parametrize def test_streaming_response_list(self, client: Ark) -> None: - with client.domains.with_streaming_response.list() as response: + with client.tenants.domains.with_streaming_response.list( + "cm6abc123def456", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -117,17 +144,26 @@ def test_streaming_response_list(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_list(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.domains.with_raw_response.list( + "", + ) + @parametrize def test_method_delete(self, client: Ark) -> None: - domain = client.domains.delete( - "domainId", + domain = client.tenants.domains.delete( + domain_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(DomainDeleteResponse, domain, path=["response"]) @parametrize def test_raw_response_delete(self, client: Ark) -> None: - response = client.domains.with_raw_response.delete( - "domainId", + response = client.tenants.domains.with_raw_response.delete( + domain_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -137,8 +173,9 @@ def test_raw_response_delete(self, client: Ark) -> None: @parametrize def test_streaming_response_delete(self, client: Ark) -> None: - with client.domains.with_streaming_response.delete( - "domainId", + with client.tenants.domains.with_streaming_response.delete( + domain_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -150,22 +187,31 @@ def test_streaming_response_delete(self, client: Ark) -> None: @parametrize def test_path_params_delete(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.domains.with_raw_response.delete( + domain_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `domain_id` but received ''"): - client.domains.with_raw_response.delete( - "", + client.tenants.domains.with_raw_response.delete( + domain_id="", + tenant_id="cm6abc123def456", ) @parametrize def test_method_verify(self, client: Ark) -> None: - domain = client.domains.verify( - "domainId", + domain = client.tenants.domains.verify( + domain_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(DomainVerifyResponse, domain, path=["response"]) @parametrize def test_raw_response_verify(self, client: Ark) -> None: - response = client.domains.with_raw_response.verify( - "domainId", + response = client.tenants.domains.with_raw_response.verify( + domain_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -175,8 +221,9 @@ def test_raw_response_verify(self, client: Ark) -> None: @parametrize def test_streaming_response_verify(self, client: Ark) -> None: - with client.domains.with_streaming_response.verify( - "domainId", + with client.tenants.domains.with_streaming_response.verify( + domain_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -188,9 +235,16 @@ def test_streaming_response_verify(self, client: Ark) -> None: @parametrize def test_path_params_verify(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.domains.with_raw_response.verify( + domain_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `domain_id` but received ''"): - client.domains.with_raw_response.verify( - "", + client.tenants.domains.with_raw_response.verify( + domain_id="", + tenant_id="cm6abc123def456", ) @@ -201,14 +255,16 @@ class TestAsyncDomains: @parametrize async def test_method_create(self, async_client: AsyncArk) -> None: - domain = await async_client.domains.create( + domain = await async_client.tenants.domains.create( + tenant_id="cm6abc123def456", name="notifications.myapp.com", ) assert_matches_type(DomainCreateResponse, domain, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncArk) -> None: - response = await async_client.domains.with_raw_response.create( + response = await async_client.tenants.domains.with_raw_response.create( + tenant_id="cm6abc123def456", name="notifications.myapp.com", ) @@ -219,7 +275,8 @@ async def test_raw_response_create(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncArk) -> None: - async with async_client.domains.with_streaming_response.create( + async with async_client.tenants.domains.with_streaming_response.create( + tenant_id="cm6abc123def456", name="notifications.myapp.com", ) as response: assert not response.is_closed @@ -230,17 +287,27 @@ async def test_streaming_response_create(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_path_params_create(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.domains.with_raw_response.create( + tenant_id="", + name="notifications.myapp.com", + ) + @parametrize async def test_method_retrieve(self, async_client: AsyncArk) -> None: - domain = await async_client.domains.retrieve( - "domainId", + domain = await async_client.tenants.domains.retrieve( + domain_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(DomainRetrieveResponse, domain, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: - response = await async_client.domains.with_raw_response.retrieve( - "domainId", + response = await async_client.tenants.domains.with_raw_response.retrieve( + domain_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -250,8 +317,9 @@ async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: - async with async_client.domains.with_streaming_response.retrieve( - "domainId", + async with async_client.tenants.domains.with_streaming_response.retrieve( + domain_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -263,19 +331,30 @@ async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None @parametrize async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.domains.with_raw_response.retrieve( + domain_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `domain_id` but received ''"): - await async_client.domains.with_raw_response.retrieve( - "", + await async_client.tenants.domains.with_raw_response.retrieve( + domain_id="", + tenant_id="cm6abc123def456", ) @parametrize async def test_method_list(self, async_client: AsyncArk) -> None: - domain = await async_client.domains.list() + domain = await async_client.tenants.domains.list( + "cm6abc123def456", + ) assert_matches_type(DomainListResponse, domain, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncArk) -> None: - response = await async_client.domains.with_raw_response.list() + response = await async_client.tenants.domains.with_raw_response.list( + "cm6abc123def456", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -284,7 +363,9 @@ async def test_raw_response_list(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncArk) -> None: - async with async_client.domains.with_streaming_response.list() as response: + async with async_client.tenants.domains.with_streaming_response.list( + "cm6abc123def456", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -293,17 +374,26 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_path_params_list(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.domains.with_raw_response.list( + "", + ) + @parametrize async def test_method_delete(self, async_client: AsyncArk) -> None: - domain = await async_client.domains.delete( - "domainId", + domain = await async_client.tenants.domains.delete( + domain_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(DomainDeleteResponse, domain, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncArk) -> None: - response = await async_client.domains.with_raw_response.delete( - "domainId", + response = await async_client.tenants.domains.with_raw_response.delete( + domain_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -313,8 +403,9 @@ async def test_raw_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: - async with async_client.domains.with_streaming_response.delete( - "domainId", + async with async_client.tenants.domains.with_streaming_response.delete( + domain_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -326,22 +417,31 @@ async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_delete(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.domains.with_raw_response.delete( + domain_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `domain_id` but received ''"): - await async_client.domains.with_raw_response.delete( - "", + await async_client.tenants.domains.with_raw_response.delete( + domain_id="", + tenant_id="cm6abc123def456", ) @parametrize async def test_method_verify(self, async_client: AsyncArk) -> None: - domain = await async_client.domains.verify( - "domainId", + domain = await async_client.tenants.domains.verify( + domain_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(DomainVerifyResponse, domain, path=["response"]) @parametrize async def test_raw_response_verify(self, async_client: AsyncArk) -> None: - response = await async_client.domains.with_raw_response.verify( - "domainId", + response = await async_client.tenants.domains.with_raw_response.verify( + domain_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -351,8 +451,9 @@ async def test_raw_response_verify(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_verify(self, async_client: AsyncArk) -> None: - async with async_client.domains.with_streaming_response.verify( - "domainId", + async with async_client.tenants.domains.with_streaming_response.verify( + domain_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -364,7 +465,14 @@ async def test_streaming_response_verify(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_verify(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.domains.with_raw_response.verify( + domain_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `domain_id` but received ''"): - await async_client.domains.with_raw_response.verify( - "", + await async_client.tenants.domains.with_raw_response.verify( + domain_id="", + tenant_id="cm6abc123def456", ) diff --git a/tests/api_resources/test_suppressions.py b/tests/api_resources/tenants/test_suppressions.py similarity index 62% rename from tests/api_resources/test_suppressions.py rename to tests/api_resources/tenants/test_suppressions.py index d7c0622..879beca 100644 --- a/tests/api_resources/test_suppressions.py +++ b/tests/api_resources/tenants/test_suppressions.py @@ -8,15 +8,14 @@ import pytest from ark import Ark, AsyncArk -from ark.types import ( +from tests.utils import assert_matches_type +from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination +from ark.types.tenants import ( SuppressionListResponse, SuppressionCreateResponse, SuppressionDeleteResponse, SuppressionRetrieveResponse, - SuppressionBulkCreateResponse, ) -from tests.utils import assert_matches_type -from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -26,14 +25,16 @@ class TestSuppressions: @parametrize def test_method_create(self, client: Ark) -> None: - suppression = client.suppressions.create( + suppression = client.tenants.suppressions.create( + tenant_id="cm6abc123def456", address="user@example.com", ) assert_matches_type(SuppressionCreateResponse, suppression, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Ark) -> None: - suppression = client.suppressions.create( + suppression = client.tenants.suppressions.create( + tenant_id="cm6abc123def456", address="user@example.com", reason="user requested removal", ) @@ -41,7 +42,8 @@ def test_method_create_with_all_params(self, client: Ark) -> None: @parametrize def test_raw_response_create(self, client: Ark) -> None: - response = client.suppressions.with_raw_response.create( + response = client.tenants.suppressions.with_raw_response.create( + tenant_id="cm6abc123def456", address="user@example.com", ) @@ -52,7 +54,8 @@ def test_raw_response_create(self, client: Ark) -> None: @parametrize def test_streaming_response_create(self, client: Ark) -> None: - with client.suppressions.with_streaming_response.create( + with client.tenants.suppressions.with_streaming_response.create( + tenant_id="cm6abc123def456", address="user@example.com", ) as response: assert not response.is_closed @@ -63,17 +66,27 @@ def test_streaming_response_create(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_create(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.suppressions.with_raw_response.create( + tenant_id="", + address="user@example.com", + ) + @parametrize def test_method_retrieve(self, client: Ark) -> None: - suppression = client.suppressions.retrieve( - "dev@stainless.com", + suppression = client.tenants.suppressions.retrieve( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert_matches_type(SuppressionRetrieveResponse, suppression, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Ark) -> None: - response = client.suppressions.with_raw_response.retrieve( - "dev@stainless.com", + response = client.tenants.suppressions.with_raw_response.retrieve( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -83,8 +96,9 @@ def test_raw_response_retrieve(self, client: Ark) -> None: @parametrize def test_streaming_response_retrieve(self, client: Ark) -> None: - with client.suppressions.with_streaming_response.retrieve( - "dev@stainless.com", + with client.tenants.suppressions.with_streaming_response.retrieve( + email="user@example.com", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -96,19 +110,29 @@ def test_streaming_response_retrieve(self, client: Ark) -> None: @parametrize def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.suppressions.with_raw_response.retrieve( + email="user@example.com", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email` but received ''"): - client.suppressions.with_raw_response.retrieve( - "", + client.tenants.suppressions.with_raw_response.retrieve( + email="", + tenant_id="cm6abc123def456", ) @parametrize def test_method_list(self, client: Ark) -> None: - suppression = client.suppressions.list() + suppression = client.tenants.suppressions.list( + tenant_id="cm6abc123def456", + ) assert_matches_type(SyncPageNumberPagination[SuppressionListResponse], suppression, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Ark) -> None: - suppression = client.suppressions.list( + suppression = client.tenants.suppressions.list( + tenant_id="cm6abc123def456", page=0, per_page=100, ) @@ -116,7 +140,9 @@ def test_method_list_with_all_params(self, client: Ark) -> None: @parametrize def test_raw_response_list(self, client: Ark) -> None: - response = client.suppressions.with_raw_response.list() + response = client.tenants.suppressions.with_raw_response.list( + tenant_id="cm6abc123def456", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -125,7 +151,9 @@ def test_raw_response_list(self, client: Ark) -> None: @parametrize def test_streaming_response_list(self, client: Ark) -> None: - with client.suppressions.with_streaming_response.list() as response: + with client.tenants.suppressions.with_streaming_response.list( + tenant_id="cm6abc123def456", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -134,17 +162,26 @@ def test_streaming_response_list(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_list(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.suppressions.with_raw_response.list( + tenant_id="", + ) + @parametrize def test_method_delete(self, client: Ark) -> None: - suppression = client.suppressions.delete( - "dev@stainless.com", + suppression = client.tenants.suppressions.delete( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert_matches_type(SuppressionDeleteResponse, suppression, path=["response"]) @parametrize def test_raw_response_delete(self, client: Ark) -> None: - response = client.suppressions.with_raw_response.delete( - "dev@stainless.com", + response = client.tenants.suppressions.with_raw_response.delete( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -154,8 +191,9 @@ def test_raw_response_delete(self, client: Ark) -> None: @parametrize def test_streaming_response_delete(self, client: Ark) -> None: - with client.suppressions.with_streaming_response.delete( - "dev@stainless.com", + with client.tenants.suppressions.with_streaming_response.delete( + email="user@example.com", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -167,41 +205,17 @@ def test_streaming_response_delete(self, client: Ark) -> None: @parametrize def test_path_params_delete(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `email` but received ''"): - client.suppressions.with_raw_response.delete( - "", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.suppressions.with_raw_response.delete( + email="user@example.com", + tenant_id="", ) - @parametrize - def test_method_bulk_create(self, client: Ark) -> None: - suppression = client.suppressions.bulk_create( - suppressions=[{"address": "dev@stainless.com"}], - ) - assert_matches_type(SuppressionBulkCreateResponse, suppression, path=["response"]) - - @parametrize - def test_raw_response_bulk_create(self, client: Ark) -> None: - response = client.suppressions.with_raw_response.bulk_create( - suppressions=[{"address": "dev@stainless.com"}], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - suppression = response.parse() - assert_matches_type(SuppressionBulkCreateResponse, suppression, path=["response"]) - - @parametrize - def test_streaming_response_bulk_create(self, client: Ark) -> None: - with client.suppressions.with_streaming_response.bulk_create( - suppressions=[{"address": "dev@stainless.com"}], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - suppression = response.parse() - assert_matches_type(SuppressionBulkCreateResponse, suppression, path=["response"]) - - assert cast(Any, response.is_closed) is True + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email` but received ''"): + client.tenants.suppressions.with_raw_response.delete( + email="", + tenant_id="cm6abc123def456", + ) class TestAsyncSuppressions: @@ -211,14 +225,16 @@ class TestAsyncSuppressions: @parametrize async def test_method_create(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.create( + suppression = await async_client.tenants.suppressions.create( + tenant_id="cm6abc123def456", address="user@example.com", ) assert_matches_type(SuppressionCreateResponse, suppression, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.create( + suppression = await async_client.tenants.suppressions.create( + tenant_id="cm6abc123def456", address="user@example.com", reason="user requested removal", ) @@ -226,7 +242,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncArk) -> No @parametrize async def test_raw_response_create(self, async_client: AsyncArk) -> None: - response = await async_client.suppressions.with_raw_response.create( + response = await async_client.tenants.suppressions.with_raw_response.create( + tenant_id="cm6abc123def456", address="user@example.com", ) @@ -237,7 +254,8 @@ async def test_raw_response_create(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncArk) -> None: - async with async_client.suppressions.with_streaming_response.create( + async with async_client.tenants.suppressions.with_streaming_response.create( + tenant_id="cm6abc123def456", address="user@example.com", ) as response: assert not response.is_closed @@ -248,17 +266,27 @@ async def test_streaming_response_create(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_path_params_create(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.suppressions.with_raw_response.create( + tenant_id="", + address="user@example.com", + ) + @parametrize async def test_method_retrieve(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.retrieve( - "dev@stainless.com", + suppression = await async_client.tenants.suppressions.retrieve( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert_matches_type(SuppressionRetrieveResponse, suppression, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: - response = await async_client.suppressions.with_raw_response.retrieve( - "dev@stainless.com", + response = await async_client.tenants.suppressions.with_raw_response.retrieve( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -268,8 +296,9 @@ async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: - async with async_client.suppressions.with_streaming_response.retrieve( - "dev@stainless.com", + async with async_client.tenants.suppressions.with_streaming_response.retrieve( + email="user@example.com", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -281,19 +310,29 @@ async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None @parametrize async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.suppressions.with_raw_response.retrieve( + email="user@example.com", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email` but received ''"): - await async_client.suppressions.with_raw_response.retrieve( - "", + await async_client.tenants.suppressions.with_raw_response.retrieve( + email="", + tenant_id="cm6abc123def456", ) @parametrize async def test_method_list(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.list() + suppression = await async_client.tenants.suppressions.list( + tenant_id="cm6abc123def456", + ) assert_matches_type(AsyncPageNumberPagination[SuppressionListResponse], suppression, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.list( + suppression = await async_client.tenants.suppressions.list( + tenant_id="cm6abc123def456", page=0, per_page=100, ) @@ -301,7 +340,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncArk) -> None @parametrize async def test_raw_response_list(self, async_client: AsyncArk) -> None: - response = await async_client.suppressions.with_raw_response.list() + response = await async_client.tenants.suppressions.with_raw_response.list( + tenant_id="cm6abc123def456", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -310,7 +351,9 @@ async def test_raw_response_list(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncArk) -> None: - async with async_client.suppressions.with_streaming_response.list() as response: + async with async_client.tenants.suppressions.with_streaming_response.list( + tenant_id="cm6abc123def456", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -319,17 +362,26 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_path_params_list(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.suppressions.with_raw_response.list( + tenant_id="", + ) + @parametrize async def test_method_delete(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.delete( - "dev@stainless.com", + suppression = await async_client.tenants.suppressions.delete( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert_matches_type(SuppressionDeleteResponse, suppression, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncArk) -> None: - response = await async_client.suppressions.with_raw_response.delete( - "dev@stainless.com", + response = await async_client.tenants.suppressions.with_raw_response.delete( + email="user@example.com", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -339,8 +391,9 @@ async def test_raw_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: - async with async_client.suppressions.with_streaming_response.delete( - "dev@stainless.com", + async with async_client.tenants.suppressions.with_streaming_response.delete( + email="user@example.com", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -352,38 +405,14 @@ async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_delete(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `email` but received ''"): - await async_client.suppressions.with_raw_response.delete( - "", + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.suppressions.with_raw_response.delete( + email="user@example.com", + tenant_id="", ) - @parametrize - async def test_method_bulk_create(self, async_client: AsyncArk) -> None: - suppression = await async_client.suppressions.bulk_create( - suppressions=[{"address": "dev@stainless.com"}], - ) - assert_matches_type(SuppressionBulkCreateResponse, suppression, path=["response"]) - - @parametrize - async def test_raw_response_bulk_create(self, async_client: AsyncArk) -> None: - response = await async_client.suppressions.with_raw_response.bulk_create( - suppressions=[{"address": "dev@stainless.com"}], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - suppression = await response.parse() - assert_matches_type(SuppressionBulkCreateResponse, suppression, path=["response"]) - - @parametrize - async def test_streaming_response_bulk_create(self, async_client: AsyncArk) -> None: - async with async_client.suppressions.with_streaming_response.bulk_create( - suppressions=[{"address": "dev@stainless.com"}], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - suppression = await response.parse() - assert_matches_type(SuppressionBulkCreateResponse, suppression, path=["response"]) - - assert cast(Any, response.is_closed) is True + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email` but received ''"): + await async_client.tenants.suppressions.with_raw_response.delete( + email="", + tenant_id="cm6abc123def456", + ) diff --git a/tests/api_resources/test_tracking.py b/tests/api_resources/tenants/test_tracking.py similarity index 61% rename from tests/api_resources/test_tracking.py rename to tests/api_resources/tenants/test_tracking.py index 7cd008b..58fb912 100644 --- a/tests/api_resources/test_tracking.py +++ b/tests/api_resources/tenants/test_tracking.py @@ -8,7 +8,8 @@ import pytest from ark import Ark, AsyncArk -from ark.types import ( +from tests.utils import assert_matches_type +from ark.types.tenants import ( TrackingListResponse, TrackingCreateResponse, TrackingDeleteResponse, @@ -16,7 +17,6 @@ TrackingVerifyResponse, TrackingRetrieveResponse, ) -from tests.utils import assert_matches_type base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -26,7 +26,8 @@ class TestTracking: @parametrize def test_method_create(self, client: Ark) -> None: - tracking = client.tracking.create( + tracking = client.tenants.tracking.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ) @@ -34,7 +35,8 @@ def test_method_create(self, client: Ark) -> None: @parametrize def test_method_create_with_all_params(self, client: Ark) -> None: - tracking = client.tracking.create( + tracking = client.tenants.tracking.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ssl_enabled=True, @@ -45,7 +47,8 @@ def test_method_create_with_all_params(self, client: Ark) -> None: @parametrize def test_raw_response_create(self, client: Ark) -> None: - response = client.tracking.with_raw_response.create( + response = client.tenants.tracking.with_raw_response.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ) @@ -57,7 +60,8 @@ def test_raw_response_create(self, client: Ark) -> None: @parametrize def test_streaming_response_create(self, client: Ark) -> None: - with client.tracking.with_streaming_response.create( + with client.tenants.tracking.with_streaming_response.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ) as response: @@ -69,17 +73,28 @@ def test_streaming_response_create(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_create(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.tracking.with_raw_response.create( + tenant_id="", + domain_id=123, + name="track", + ) + @parametrize def test_method_retrieve(self, client: Ark) -> None: - tracking = client.tracking.retrieve( - "trackingId", + tracking = client.tenants.tracking.retrieve( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingRetrieveResponse, tracking, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Ark) -> None: - response = client.tracking.with_raw_response.retrieve( - "trackingId", + response = client.tenants.tracking.with_raw_response.retrieve( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -89,8 +104,9 @@ def test_raw_response_retrieve(self, client: Ark) -> None: @parametrize def test_streaming_response_retrieve(self, client: Ark) -> None: - with client.tracking.with_streaming_response.retrieve( - "trackingId", + with client.tenants.tracking.with_streaming_response.retrieve( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -102,22 +118,31 @@ def test_streaming_response_retrieve(self, client: Ark) -> None: @parametrize def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.tracking.with_raw_response.retrieve( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - client.tracking.with_raw_response.retrieve( - "", + client.tenants.tracking.with_raw_response.retrieve( + tracking_id="", + tenant_id="cm6abc123def456", ) @parametrize def test_method_update(self, client: Ark) -> None: - tracking = client.tracking.update( - tracking_id="trackingId", + tracking = client.tenants.tracking.update( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingUpdateResponse, tracking, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Ark) -> None: - tracking = client.tracking.update( - tracking_id="trackingId", + tracking = client.tenants.tracking.update( + tracking_id="123", + tenant_id="cm6abc123def456", excluded_click_domains="example.com,mysite.org", ssl_enabled=True, track_clicks=True, @@ -127,8 +152,9 @@ def test_method_update_with_all_params(self, client: Ark) -> None: @parametrize def test_raw_response_update(self, client: Ark) -> None: - response = client.tracking.with_raw_response.update( - tracking_id="trackingId", + response = client.tenants.tracking.with_raw_response.update( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -138,8 +164,9 @@ def test_raw_response_update(self, client: Ark) -> None: @parametrize def test_streaming_response_update(self, client: Ark) -> None: - with client.tracking.with_streaming_response.update( - tracking_id="trackingId", + with client.tenants.tracking.with_streaming_response.update( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -151,19 +178,30 @@ def test_streaming_response_update(self, client: Ark) -> None: @parametrize def test_path_params_update(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.tracking.with_raw_response.update( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - client.tracking.with_raw_response.update( + client.tenants.tracking.with_raw_response.update( tracking_id="", + tenant_id="cm6abc123def456", ) @parametrize def test_method_list(self, client: Ark) -> None: - tracking = client.tracking.list() + tracking = client.tenants.tracking.list( + "cm6abc123def456", + ) assert_matches_type(TrackingListResponse, tracking, path=["response"]) @parametrize def test_raw_response_list(self, client: Ark) -> None: - response = client.tracking.with_raw_response.list() + response = client.tenants.tracking.with_raw_response.list( + "cm6abc123def456", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -172,7 +210,9 @@ def test_raw_response_list(self, client: Ark) -> None: @parametrize def test_streaming_response_list(self, client: Ark) -> None: - with client.tracking.with_streaming_response.list() as response: + with client.tenants.tracking.with_streaming_response.list( + "cm6abc123def456", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -181,17 +221,26 @@ def test_streaming_response_list(self, client: Ark) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_path_params_list(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.tracking.with_raw_response.list( + "", + ) + @parametrize def test_method_delete(self, client: Ark) -> None: - tracking = client.tracking.delete( - "trackingId", + tracking = client.tenants.tracking.delete( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingDeleteResponse, tracking, path=["response"]) @parametrize def test_raw_response_delete(self, client: Ark) -> None: - response = client.tracking.with_raw_response.delete( - "trackingId", + response = client.tenants.tracking.with_raw_response.delete( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -201,8 +250,9 @@ def test_raw_response_delete(self, client: Ark) -> None: @parametrize def test_streaming_response_delete(self, client: Ark) -> None: - with client.tracking.with_streaming_response.delete( - "trackingId", + with client.tenants.tracking.with_streaming_response.delete( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -214,22 +264,31 @@ def test_streaming_response_delete(self, client: Ark) -> None: @parametrize def test_path_params_delete(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.tracking.with_raw_response.delete( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - client.tracking.with_raw_response.delete( - "", + client.tenants.tracking.with_raw_response.delete( + tracking_id="", + tenant_id="cm6abc123def456", ) @parametrize def test_method_verify(self, client: Ark) -> None: - tracking = client.tracking.verify( - "trackingId", + tracking = client.tenants.tracking.verify( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingVerifyResponse, tracking, path=["response"]) @parametrize def test_raw_response_verify(self, client: Ark) -> None: - response = client.tracking.with_raw_response.verify( - "trackingId", + response = client.tenants.tracking.with_raw_response.verify( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -239,8 +298,9 @@ def test_raw_response_verify(self, client: Ark) -> None: @parametrize def test_streaming_response_verify(self, client: Ark) -> None: - with client.tracking.with_streaming_response.verify( - "trackingId", + with client.tenants.tracking.with_streaming_response.verify( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -252,9 +312,16 @@ def test_streaming_response_verify(self, client: Ark) -> None: @parametrize def test_path_params_verify(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.tracking.with_raw_response.verify( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - client.tracking.with_raw_response.verify( - "", + client.tenants.tracking.with_raw_response.verify( + tracking_id="", + tenant_id="cm6abc123def456", ) @@ -265,7 +332,8 @@ class TestAsyncTracking: @parametrize async def test_method_create(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.create( + tracking = await async_client.tenants.tracking.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ) @@ -273,7 +341,8 @@ async def test_method_create(self, async_client: AsyncArk) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.create( + tracking = await async_client.tenants.tracking.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ssl_enabled=True, @@ -284,7 +353,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncArk) -> No @parametrize async def test_raw_response_create(self, async_client: AsyncArk) -> None: - response = await async_client.tracking.with_raw_response.create( + response = await async_client.tenants.tracking.with_raw_response.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ) @@ -296,7 +366,8 @@ async def test_raw_response_create(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncArk) -> None: - async with async_client.tracking.with_streaming_response.create( + async with async_client.tenants.tracking.with_streaming_response.create( + tenant_id="cm6abc123def456", domain_id=123, name="track", ) as response: @@ -308,17 +379,28 @@ async def test_streaming_response_create(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_path_params_create(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.tracking.with_raw_response.create( + tenant_id="", + domain_id=123, + name="track", + ) + @parametrize async def test_method_retrieve(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.retrieve( - "trackingId", + tracking = await async_client.tenants.tracking.retrieve( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingRetrieveResponse, tracking, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: - response = await async_client.tracking.with_raw_response.retrieve( - "trackingId", + response = await async_client.tenants.tracking.with_raw_response.retrieve( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -328,8 +410,9 @@ async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: - async with async_client.tracking.with_streaming_response.retrieve( - "trackingId", + async with async_client.tenants.tracking.with_streaming_response.retrieve( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -341,22 +424,31 @@ async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None @parametrize async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.tracking.with_raw_response.retrieve( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - await async_client.tracking.with_raw_response.retrieve( - "", + await async_client.tenants.tracking.with_raw_response.retrieve( + tracking_id="", + tenant_id="cm6abc123def456", ) @parametrize async def test_method_update(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.update( - tracking_id="trackingId", + tracking = await async_client.tenants.tracking.update( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingUpdateResponse, tracking, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.update( - tracking_id="trackingId", + tracking = await async_client.tenants.tracking.update( + tracking_id="123", + tenant_id="cm6abc123def456", excluded_click_domains="example.com,mysite.org", ssl_enabled=True, track_clicks=True, @@ -366,8 +458,9 @@ async def test_method_update_with_all_params(self, async_client: AsyncArk) -> No @parametrize async def test_raw_response_update(self, async_client: AsyncArk) -> None: - response = await async_client.tracking.with_raw_response.update( - tracking_id="trackingId", + response = await async_client.tenants.tracking.with_raw_response.update( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -377,8 +470,9 @@ async def test_raw_response_update(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncArk) -> None: - async with async_client.tracking.with_streaming_response.update( - tracking_id="trackingId", + async with async_client.tenants.tracking.with_streaming_response.update( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -390,19 +484,30 @@ async def test_streaming_response_update(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_update(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.tracking.with_raw_response.update( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - await async_client.tracking.with_raw_response.update( + await async_client.tenants.tracking.with_raw_response.update( tracking_id="", + tenant_id="cm6abc123def456", ) @parametrize async def test_method_list(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.list() + tracking = await async_client.tenants.tracking.list( + "cm6abc123def456", + ) assert_matches_type(TrackingListResponse, tracking, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncArk) -> None: - response = await async_client.tracking.with_raw_response.list() + response = await async_client.tenants.tracking.with_raw_response.list( + "cm6abc123def456", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -411,7 +516,9 @@ async def test_raw_response_list(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncArk) -> None: - async with async_client.tracking.with_streaming_response.list() as response: + async with async_client.tenants.tracking.with_streaming_response.list( + "cm6abc123def456", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -420,17 +527,26 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_path_params_list(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.tracking.with_raw_response.list( + "", + ) + @parametrize async def test_method_delete(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.delete( - "trackingId", + tracking = await async_client.tenants.tracking.delete( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingDeleteResponse, tracking, path=["response"]) @parametrize async def test_raw_response_delete(self, async_client: AsyncArk) -> None: - response = await async_client.tracking.with_raw_response.delete( - "trackingId", + response = await async_client.tenants.tracking.with_raw_response.delete( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -440,8 +556,9 @@ async def test_raw_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: - async with async_client.tracking.with_streaming_response.delete( - "trackingId", + async with async_client.tenants.tracking.with_streaming_response.delete( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -453,22 +570,31 @@ async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_delete(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.tracking.with_raw_response.delete( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - await async_client.tracking.with_raw_response.delete( - "", + await async_client.tenants.tracking.with_raw_response.delete( + tracking_id="", + tenant_id="cm6abc123def456", ) @parametrize async def test_method_verify(self, async_client: AsyncArk) -> None: - tracking = await async_client.tracking.verify( - "trackingId", + tracking = await async_client.tenants.tracking.verify( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert_matches_type(TrackingVerifyResponse, tracking, path=["response"]) @parametrize async def test_raw_response_verify(self, async_client: AsyncArk) -> None: - response = await async_client.tracking.with_raw_response.verify( - "trackingId", + response = await async_client.tenants.tracking.with_raw_response.verify( + tracking_id="123", + tenant_id="cm6abc123def456", ) assert response.is_closed is True @@ -478,8 +604,9 @@ async def test_raw_response_verify(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_verify(self, async_client: AsyncArk) -> None: - async with async_client.tracking.with_streaming_response.verify( - "trackingId", + async with async_client.tenants.tracking.with_streaming_response.verify( + tracking_id="123", + tenant_id="cm6abc123def456", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -491,7 +618,14 @@ async def test_streaming_response_verify(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_verify(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.tracking.with_raw_response.verify( + tracking_id="123", + tenant_id="", + ) + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tracking_id` but received ''"): - await async_client.tracking.with_raw_response.verify( - "", + await async_client.tenants.tracking.with_raw_response.verify( + tracking_id="", + tenant_id="cm6abc123def456", ) diff --git a/tests/api_resources/tenants/test_usage.py b/tests/api_resources/tenants/test_usage.py new file mode 100644 index 0000000..edb2820 --- /dev/null +++ b/tests/api_resources/tenants/test_usage.py @@ -0,0 +1,217 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from ark import Ark, AsyncArk +from tests.utils import assert_matches_type +from ark.types.tenants import ( + UsageRetrieveResponse, + UsageRetrieveTimeseriesResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsage: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Ark) -> None: + usage = client.tenants.usage.retrieve( + tenant_id="cm6abc123def456", + ) + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Ark) -> None: + usage = client.tenants.usage.retrieve( + tenant_id="cm6abc123def456", + period="period", + timezone="timezone", + ) + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Ark) -> None: + response = client.tenants.usage.with_raw_response.retrieve( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Ark) -> None: + with client.tenants.usage.with_streaming_response.retrieve( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.usage.with_raw_response.retrieve( + tenant_id="", + ) + + @parametrize + def test_method_retrieve_timeseries(self, client: Ark) -> None: + usage = client.tenants.usage.retrieve_timeseries( + tenant_id="cm6abc123def456", + ) + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + @parametrize + def test_method_retrieve_timeseries_with_all_params(self, client: Ark) -> None: + usage = client.tenants.usage.retrieve_timeseries( + tenant_id="cm6abc123def456", + granularity="hour", + period="period", + timezone="timezone", + ) + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_retrieve_timeseries(self, client: Ark) -> None: + response = client.tenants.usage.with_raw_response.retrieve_timeseries( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_timeseries(self, client: Ark) -> None: + with client.tenants.usage.with_streaming_response.retrieve_timeseries( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_timeseries(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.usage.with_raw_response.retrieve_timeseries( + tenant_id="", + ) + + +class TestAsyncUsage: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncArk) -> None: + usage = await async_client.tenants.usage.retrieve( + tenant_id="cm6abc123def456", + ) + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> None: + usage = await async_client.tenants.usage.retrieve( + tenant_id="cm6abc123def456", + period="period", + timezone="timezone", + ) + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.usage.with_raw_response.retrieve( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = await response.parse() + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: + async with async_client.tenants.usage.with_streaming_response.retrieve( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.usage.with_raw_response.retrieve( + tenant_id="", + ) + + @parametrize + async def test_method_retrieve_timeseries(self, async_client: AsyncArk) -> None: + usage = await async_client.tenants.usage.retrieve_timeseries( + tenant_id="cm6abc123def456", + ) + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + @parametrize + async def test_method_retrieve_timeseries_with_all_params(self, async_client: AsyncArk) -> None: + usage = await async_client.tenants.usage.retrieve_timeseries( + tenant_id="cm6abc123def456", + granularity="hour", + period="period", + timezone="timezone", + ) + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_timeseries(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.usage.with_raw_response.retrieve_timeseries( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = await response.parse() + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_timeseries(self, async_client: AsyncArk) -> None: + async with async_client.tenants.usage.with_streaming_response.retrieve_timeseries( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageRetrieveTimeseriesResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_timeseries(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.usage.with_raw_response.retrieve_timeseries( + tenant_id="", + ) diff --git a/tests/api_resources/tenants/test_webhooks.py b/tests/api_resources/tenants/test_webhooks.py new file mode 100644 index 0000000..8a5284f --- /dev/null +++ b/tests/api_resources/tenants/test_webhooks.py @@ -0,0 +1,1010 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from ark import Ark, AsyncArk +from tests.utils import assert_matches_type +from ark.types.tenants import ( + WebhookListResponse, + WebhookTestResponse, + WebhookCreateResponse, + WebhookDeleteResponse, + WebhookUpdateResponse, + WebhookRetrieveResponse, + WebhookListDeliveriesResponse, + WebhookReplayDeliveryResponse, + WebhookRetrieveDeliveryResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestWebhooks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Ark) -> None: + webhook = client.tenants.webhooks.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Ark) -> None: + webhook = client.tenants.webhooks.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + all_events=True, + enabled=True, + events=["MessageSent", "MessageDeliveryFailed", "MessageBounced"], + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_create(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.create( + tenant_id="", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) + + @parametrize + def test_method_retrieve(self, client: Ark) -> None: + webhook = client.tenants.webhooks.retrieve( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.retrieve( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.retrieve( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.retrieve( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.retrieve( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + def test_method_update(self, client: Ark) -> None: + webhook = client.tenants.webhooks.update( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Ark) -> None: + webhook = client.tenants.webhooks.update( + webhook_id="123", + tenant_id="cm6abc123def456", + all_events=True, + enabled=True, + events=["string"], + name="name", + url="https://example.com", + ) + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.update( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.update( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.update( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.update( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + def test_method_list(self, client: Ark) -> None: + webhook = client.tenants.webhooks.list( + "cm6abc123def456", + ) + assert_matches_type(WebhookListResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.list( + "cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookListResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.list( + "cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookListResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.list( + "", + ) + + @parametrize + def test_method_delete(self, client: Ark) -> None: + webhook = client.tenants.webhooks.delete( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.delete( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.delete( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.delete( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.delete( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + def test_method_list_deliveries(self, client: Ark) -> None: + webhook = client.tenants.webhooks.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + @parametrize + def test_method_list_deliveries_with_all_params(self, client: Ark) -> None: + webhook = client.tenants.webhooks.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + after=0, + before=0, + event="MessageSent", + page=1, + per_page=1, + success=True, + ) + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_list_deliveries(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_list_deliveries(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_list_deliveries(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.list_deliveries( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.list_deliveries( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + def test_method_replay_delivery(self, client: Ark) -> None: + webhook = client.tenants.webhooks.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_replay_delivery(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_replay_delivery(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_replay_delivery(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="", + webhook_id="123", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): + client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + @parametrize + def test_method_retrieve_delivery(self, client: Ark) -> None: + webhook = client.tenants.webhooks.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_retrieve_delivery(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_retrieve_delivery(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_delivery(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="", + webhook_id="123", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): + client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + @parametrize + def test_method_test(self, client: Ark) -> None: + webhook = client.tenants.webhooks.test( + webhook_id="123", + tenant_id="cm6abc123def456", + event="MessageSent", + ) + assert_matches_type(WebhookTestResponse, webhook, path=["response"]) + + @parametrize + def test_raw_response_test(self, client: Ark) -> None: + response = client.tenants.webhooks.with_raw_response.test( + webhook_id="123", + tenant_id="cm6abc123def456", + event="MessageSent", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookTestResponse, webhook, path=["response"]) + + @parametrize + def test_streaming_response_test(self, client: Ark) -> None: + with client.tenants.webhooks.with_streaming_response.test( + webhook_id="123", + tenant_id="cm6abc123def456", + event="MessageSent", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookTestResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_test(self, client: Ark) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + client.tenants.webhooks.with_raw_response.test( + webhook_id="123", + tenant_id="", + event="MessageSent", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + client.tenants.webhooks.with_raw_response.test( + webhook_id="", + tenant_id="cm6abc123def456", + event="MessageSent", + ) + + +class TestAsyncWebhooks: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + all_events=True, + enabled=True, + events=["MessageSent", "MessageDeliveryFailed", "MessageBounced"], + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.create( + tenant_id="cm6abc123def456", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_create(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.create( + tenant_id="", + name="My App Webhook", + url="https://myapp.com/webhooks/email", + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.retrieve( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.retrieve( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.retrieve( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookRetrieveResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.retrieve( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.retrieve( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.update( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.update( + webhook_id="123", + tenant_id="cm6abc123def456", + all_events=True, + enabled=True, + events=["string"], + name="name", + url="https://example.com", + ) + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.update( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.update( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookUpdateResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.update( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.update( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.list( + "cm6abc123def456", + ) + assert_matches_type(WebhookListResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.list( + "cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookListResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.list( + "cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookListResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.list( + "", + ) + + @parametrize + async def test_method_delete(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.delete( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.delete( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.delete( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookDeleteResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.delete( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.delete( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + async def test_method_list_deliveries(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + @parametrize + async def test_method_list_deliveries_with_all_params(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + after=0, + before=0, + event="MessageSent", + page=1, + per_page=1, + success=True, + ) + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_list_deliveries(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_list_deliveries(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.list_deliveries( + webhook_id="123", + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookListDeliveriesResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_list_deliveries(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.list_deliveries( + webhook_id="123", + tenant_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.list_deliveries( + webhook_id="", + tenant_id="cm6abc123def456", + ) + + @parametrize + async def test_method_replay_delivery(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_replay_delivery(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_replay_delivery(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookReplayDeliveryResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_replay_delivery(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="", + webhook_id="123", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.replay_delivery( + delivery_id="", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + @parametrize + async def test_method_retrieve_delivery(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_retrieve_delivery(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_delivery(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookRetrieveDeliveryResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_delivery(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="", + webhook_id="123", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="whr_abc123def456", + tenant_id="cm6abc123def456", + webhook_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `delivery_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.retrieve_delivery( + delivery_id="", + tenant_id="cm6abc123def456", + webhook_id="123", + ) + + @parametrize + async def test_method_test(self, async_client: AsyncArk) -> None: + webhook = await async_client.tenants.webhooks.test( + webhook_id="123", + tenant_id="cm6abc123def456", + event="MessageSent", + ) + assert_matches_type(WebhookTestResponse, webhook, path=["response"]) + + @parametrize + async def test_raw_response_test(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.webhooks.with_raw_response.test( + webhook_id="123", + tenant_id="cm6abc123def456", + event="MessageSent", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookTestResponse, webhook, path=["response"]) + + @parametrize + async def test_streaming_response_test(self, async_client: AsyncArk) -> None: + async with async_client.tenants.webhooks.with_streaming_response.test( + webhook_id="123", + tenant_id="cm6abc123def456", + event="MessageSent", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookTestResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_test(self, async_client: AsyncArk) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `tenant_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.test( + webhook_id="123", + tenant_id="", + event="MessageSent", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `webhook_id` but received ''"): + await async_client.tenants.webhooks.with_raw_response.test( + webhook_id="", + tenant_id="cm6abc123def456", + event="MessageSent", + ) diff --git a/tests/api_resources/test_emails.py b/tests/api_resources/test_emails.py index 7a54df7..0e37b72 100644 --- a/tests/api_resources/test_emails.py +++ b/tests/api_resources/test_emails.py @@ -29,14 +29,14 @@ class TestEmails: @parametrize def test_method_retrieve(self, client: Ark) -> None: email = client.emails.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", ) assert_matches_type(EmailRetrieveResponse, email, path=["response"]) @parametrize def test_method_retrieve_with_all_params(self, client: Ark) -> None: email = client.emails.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", expand="full", ) assert_matches_type(EmailRetrieveResponse, email, path=["response"]) @@ -44,7 +44,7 @@ def test_method_retrieve_with_all_params(self, client: Ark) -> None: @parametrize def test_raw_response_retrieve(self, client: Ark) -> None: response = client.emails.with_raw_response.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", ) assert response.is_closed is True @@ -55,7 +55,7 @@ def test_raw_response_retrieve(self, client: Ark) -> None: @parametrize def test_streaming_response_retrieve(self, client: Ark) -> None: with client.emails.with_streaming_response.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -67,9 +67,9 @@ def test_streaming_response_retrieve(self, client: Ark) -> None: @parametrize def test_path_params_retrieve(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_id` but received ''"): client.emails.with_raw_response.retrieve( - id="", + email_id="", ) @parametrize @@ -144,7 +144,7 @@ def test_streaming_response_retrieve_deliveries(self, client: Ark) -> None: @parametrize def test_path_params_retrieve_deliveries(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_id` but received ''"): client.emails.with_raw_response.retrieve_deliveries( "", ) @@ -182,7 +182,7 @@ def test_streaming_response_retry(self, client: Ark) -> None: @parametrize def test_path_params_retry(self, client: Ark) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_id` but received ''"): client.emails.with_raw_response.retry( "", ) @@ -403,14 +403,14 @@ class TestAsyncEmails: @parametrize async def test_method_retrieve(self, async_client: AsyncArk) -> None: email = await async_client.emails.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", ) assert_matches_type(EmailRetrieveResponse, email, path=["response"]) @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> None: email = await async_client.emails.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", expand="full", ) assert_matches_type(EmailRetrieveResponse, email, path=["response"]) @@ -418,7 +418,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> @parametrize async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: response = await async_client.emails.with_raw_response.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", ) assert response.is_closed is True @@ -429,7 +429,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: async with async_client.emails.with_streaming_response.retrieve( - id="aBc123XyZ", + email_id="aBc123XyZ", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -441,9 +441,9 @@ async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None @parametrize async def test_path_params_retrieve(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_id` but received ''"): await async_client.emails.with_raw_response.retrieve( - id="", + email_id="", ) @parametrize @@ -518,7 +518,7 @@ async def test_streaming_response_retrieve_deliveries(self, async_client: AsyncA @parametrize async def test_path_params_retrieve_deliveries(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_id` but received ''"): await async_client.emails.with_raw_response.retrieve_deliveries( "", ) @@ -556,7 +556,7 @@ async def test_streaming_response_retry(self, async_client: AsyncArk) -> None: @parametrize async def test_path_params_retry(self, async_client: AsyncArk) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `email_id` but received ''"): await async_client.emails.with_raw_response.retry( "", ) diff --git a/tests/api_resources/test_limits.py b/tests/api_resources/test_limits.py new file mode 100644 index 0000000..f303d24 --- /dev/null +++ b/tests/api_resources/test_limits.py @@ -0,0 +1,74 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from ark import Ark, AsyncArk +from ark.types import LimitRetrieveResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestLimits: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Ark) -> None: + limit = client.limits.retrieve() + assert_matches_type(LimitRetrieveResponse, limit, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Ark) -> None: + response = client.limits.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + limit = response.parse() + assert_matches_type(LimitRetrieveResponse, limit, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Ark) -> None: + with client.limits.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + limit = response.parse() + assert_matches_type(LimitRetrieveResponse, limit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncLimits: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncArk) -> None: + limit = await async_client.limits.retrieve() + assert_matches_type(LimitRetrieveResponse, limit, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: + response = await async_client.limits.with_raw_response.retrieve() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + limit = await response.parse() + assert_matches_type(LimitRetrieveResponse, limit, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: + async with async_client.limits.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + limit = await response.parse() + assert_matches_type(LimitRetrieveResponse, limit, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_tenants.py b/tests/api_resources/test_tenants.py index 0f02dd2..0d4c436 100644 --- a/tests/api_resources/test_tenants.py +++ b/tests/api_resources/test_tenants.py @@ -37,7 +37,7 @@ def test_method_create_with_all_params(self, client: Ark) -> None: name="Acme Corp", metadata={ "plan": "pro", - "internal_id": "cust_12345", + "internalId": "cust_12345", "region": "us-west", }, ) @@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: Ark) -> None: tenant_id="cm6abc123def456", metadata={ "plan": "pro", - "internal_id": "cust_12345", + "internalId": "cust_12345", "region": "us-west", }, name="Acme Corporation", @@ -248,7 +248,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncArk) -> No name="Acme Corp", metadata={ "plan": "pro", - "internal_id": "cust_12345", + "internalId": "cust_12345", "region": "us-west", }, ) @@ -329,7 +329,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncArk) -> No tenant_id="cm6abc123def456", metadata={ "plan": "pro", - "internal_id": "cust_12345", + "internalId": "cust_12345", "region": "us-west", }, name="Acme Corporation", diff --git a/tests/api_resources/test_usage.py b/tests/api_resources/test_usage.py index 767da80..69d4699 100644 --- a/tests/api_resources/test_usage.py +++ b/tests/api_resources/test_usage.py @@ -8,8 +8,13 @@ import pytest from ark import Ark, AsyncArk -from ark.types import UsageRetrieveResponse +from ark.types import ( + OrgUsageSummary, + TenantUsageItem, + UsageExportResponse, +) from tests.utils import assert_matches_type +from ark.pagination import SyncPageNumberPagination, AsyncPageNumberPagination base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -20,7 +25,15 @@ class TestUsage: @parametrize def test_method_retrieve(self, client: Ark) -> None: usage = client.usage.retrieve() - assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) + + @parametrize + def test_method_retrieve_with_all_params(self, client: Ark) -> None: + usage = client.usage.retrieve( + period="period", + timezone="timezone", + ) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Ark) -> None: @@ -29,7 +42,7 @@ def test_raw_response_retrieve(self, client: Ark) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" usage = response.parse() - assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Ark) -> None: @@ -38,7 +51,81 @@ def test_streaming_response_retrieve(self, client: Ark) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" usage = response.parse() - assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_export(self, client: Ark) -> None: + usage = client.usage.export() + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + @parametrize + def test_method_export_with_all_params(self, client: Ark) -> None: + usage = client.usage.export( + format="csv", + min_sent=0, + period="period", + status="active", + timezone="timezone", + ) + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + @parametrize + def test_raw_response_export(self, client: Ark) -> None: + response = client.usage.with_raw_response.export() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + @parametrize + def test_streaming_response_export(self, client: Ark) -> None: + with client.usage.with_streaming_response.export() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_list_tenants(self, client: Ark) -> None: + usage = client.usage.list_tenants() + assert_matches_type(SyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) + + @parametrize + def test_method_list_tenants_with_all_params(self, client: Ark) -> None: + usage = client.usage.list_tenants( + min_sent=0, + page=1, + period="period", + per_page=1, + sort="sent", + status="active", + timezone="timezone", + ) + assert_matches_type(SyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) + + @parametrize + def test_raw_response_list_tenants(self, client: Ark) -> None: + response = client.usage.with_raw_response.list_tenants() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = response.parse() + assert_matches_type(SyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) + + @parametrize + def test_streaming_response_list_tenants(self, client: Ark) -> None: + with client.usage.with_streaming_response.list_tenants() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = response.parse() + assert_matches_type(SyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) assert cast(Any, response.is_closed) is True @@ -51,7 +138,15 @@ class TestAsyncUsage: @parametrize async def test_method_retrieve(self, async_client: AsyncArk) -> None: usage = await async_client.usage.retrieve() - assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) + + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncArk) -> None: + usage = await async_client.usage.retrieve( + period="period", + timezone="timezone", + ) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: @@ -60,7 +155,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" usage = await response.parse() - assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: @@ -69,6 +164,80 @@ async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None assert response.http_request.headers.get("X-Stainless-Lang") == "python" usage = await response.parse() - assert_matches_type(UsageRetrieveResponse, usage, path=["response"]) + assert_matches_type(OrgUsageSummary, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_export(self, async_client: AsyncArk) -> None: + usage = await async_client.usage.export() + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + @parametrize + async def test_method_export_with_all_params(self, async_client: AsyncArk) -> None: + usage = await async_client.usage.export( + format="csv", + min_sent=0, + period="period", + status="active", + timezone="timezone", + ) + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + @parametrize + async def test_raw_response_export(self, async_client: AsyncArk) -> None: + response = await async_client.usage.with_raw_response.export() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = await response.parse() + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + @parametrize + async def test_streaming_response_export(self, async_client: AsyncArk) -> None: + async with async_client.usage.with_streaming_response.export() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(UsageExportResponse, usage, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_list_tenants(self, async_client: AsyncArk) -> None: + usage = await async_client.usage.list_tenants() + assert_matches_type(AsyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) + + @parametrize + async def test_method_list_tenants_with_all_params(self, async_client: AsyncArk) -> None: + usage = await async_client.usage.list_tenants( + min_sent=0, + page=1, + period="period", + per_page=1, + sort="sent", + status="active", + timezone="timezone", + ) + assert_matches_type(AsyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) + + @parametrize + async def test_raw_response_list_tenants(self, async_client: AsyncArk) -> None: + response = await async_client.usage.with_raw_response.list_tenants() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage = await response.parse() + assert_matches_type(AsyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) + + @parametrize + async def test_streaming_response_list_tenants(self, async_client: AsyncArk) -> None: + async with async_client.usage.with_streaming_response.list_tenants() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage = await response.parse() + assert_matches_type(AsyncPageNumberPagination[TenantUsageItem], usage, path=["response"]) assert cast(Any, response.is_closed) is True