diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b4e9013..6db19b9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.16.0" + ".": "0.17.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 69b13d9..576e5df 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 35 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-39b91ffd46b6e41924f8465ffaaff6ba3c200a68daa513d4f1eb1e4b29aba78f.yml -openapi_spec_hash: 542dd50007316698c83e8b0bdd5e40e2 -config_hash: 77a3908ee910a8019f5831d3a3d53c18 +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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b2c54d3..5a605d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 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) + +### Features + +* **api:** Add Tenants ([8ba85ac](https://github.com/ArkHQ-io/ark-python/commit/8ba85ac06c8e1d803f2cf5077a1ff99d4655e178)) +* **api:** api update ([eed2900](https://github.com/ArkHQ-io/ark-python/commit/eed2900c69e717e346fe6d5d10d95be29771e233)) +* **api:** manual updates ([f355920](https://github.com/ArkHQ-io/ark-python/commit/f355920e2008cf0d5990b15c7292483279ee671c)) +* **api:** manual updates ([0e5c6fe](https://github.com/ArkHQ-io/ark-python/commit/0e5c6fe1479ca123629e23c842fc3cdf231876e5)) +* **api:** manual updates ([e310cbd](https://github.com/ArkHQ-io/ark-python/commit/e310cbdd0ce739190f1094b6ea1c5dd539eb91c4)) + ## 0.16.0 (2026-01-30) Full Changelog: [v0.15.0...v0.16.0](https://github.com/ArkHQ-io/ark-python/compare/v0.15.0...v0.16.0) diff --git a/api.md b/api.md index 7baaad3..19e5c9f 100644 --- a/api.md +++ b/api.md @@ -154,3 +154,25 @@ from ark.types import UsageRetrieveResponse Methods: - client.usage.retrieve() -> UsageRetrieveResponse + +# 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 diff --git a/pyproject.toml b/pyproject.toml index c95a3cf..1fc344a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ark-email" -version = "0.16.0" +version = "0.17.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 4a2f2a8..51fb70d 100644 --- a/src/ark/_client.py +++ b/src/ark/_client.py @@ -31,11 +31,12 @@ ) if TYPE_CHECKING: - from .resources import logs, usage, emails, domains, tracking, webhooks, suppressions + from .resources import logs, usage, emails, domains, tenants, tracking, webhooks, suppressions 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 @@ -140,6 +141,12 @@ def usage(self) -> UsageResource: return UsageResource(self) + @cached_property + def tenants(self) -> TenantsResource: + from .resources.tenants import TenantsResource + + return TenantsResource(self) + @cached_property def with_raw_response(self) -> ArkWithRawResponse: return ArkWithRawResponse(self) @@ -350,6 +357,12 @@ def usage(self) -> AsyncUsageResource: return AsyncUsageResource(self) + @cached_property + def tenants(self) -> AsyncTenantsResource: + from .resources.tenants import AsyncTenantsResource + + return AsyncTenantsResource(self) + @cached_property def with_raw_response(self) -> AsyncArkWithRawResponse: return AsyncArkWithRawResponse(self) @@ -511,6 +524,12 @@ def usage(self) -> usage.UsageResourceWithRawResponse: return UsageResourceWithRawResponse(self._client.usage) + @cached_property + def tenants(self) -> tenants.TenantsResourceWithRawResponse: + from .resources.tenants import TenantsResourceWithRawResponse + + return TenantsResourceWithRawResponse(self._client.tenants) + class AsyncArkWithRawResponse: _client: AsyncArk @@ -560,6 +579,12 @@ def usage(self) -> usage.AsyncUsageResourceWithRawResponse: return AsyncUsageResourceWithRawResponse(self._client.usage) + @cached_property + def tenants(self) -> tenants.AsyncTenantsResourceWithRawResponse: + from .resources.tenants import AsyncTenantsResourceWithRawResponse + + return AsyncTenantsResourceWithRawResponse(self._client.tenants) + class ArkWithStreamedResponse: _client: Ark @@ -609,6 +634,12 @@ def usage(self) -> usage.UsageResourceWithStreamingResponse: return UsageResourceWithStreamingResponse(self._client.usage) + @cached_property + def tenants(self) -> tenants.TenantsResourceWithStreamingResponse: + from .resources.tenants import TenantsResourceWithStreamingResponse + + return TenantsResourceWithStreamingResponse(self._client.tenants) + class AsyncArkWithStreamedResponse: _client: AsyncArk @@ -658,6 +689,12 @@ def usage(self) -> usage.AsyncUsageResourceWithStreamingResponse: return AsyncUsageResourceWithStreamingResponse(self._client.usage) + @cached_property + def tenants(self) -> tenants.AsyncTenantsResourceWithStreamingResponse: + from .resources.tenants import AsyncTenantsResourceWithStreamingResponse + + return AsyncTenantsResourceWithStreamingResponse(self._client.tenants) + Client = Ark diff --git a/src/ark/_version.py b/src/ark/_version.py index 1f10948..8bd9d9e 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.16.0" # x-release-please-version +__version__ = "0.17.0" # x-release-please-version diff --git a/src/ark/resources/__init__.py b/src/ark/resources/__init__.py index 76b59c0..f3812e4 100644 --- a/src/ark/resources/__init__.py +++ b/src/ark/resources/__init__.py @@ -32,6 +32,14 @@ DomainsResourceWithStreamingResponse, AsyncDomainsResourceWithStreamingResponse, ) +from .tenants import ( + TenantsResource, + AsyncTenantsResource, + TenantsResourceWithRawResponse, + AsyncTenantsResourceWithRawResponse, + TenantsResourceWithStreamingResponse, + AsyncTenantsResourceWithStreamingResponse, +) from .tracking import ( TrackingResource, AsyncTrackingResource, @@ -100,4 +108,10 @@ "AsyncUsageResourceWithRawResponse", "UsageResourceWithStreamingResponse", "AsyncUsageResourceWithStreamingResponse", + "TenantsResource", + "AsyncTenantsResource", + "TenantsResourceWithRawResponse", + "AsyncTenantsResourceWithRawResponse", + "TenantsResourceWithStreamingResponse", + "AsyncTenantsResourceWithStreamingResponse", ] diff --git a/src/ark/resources/tenants.py b/src/ark/resources/tenants.py new file mode 100644 index 0000000..e7b13a1 --- /dev/null +++ b/src/ark/resources/tenants.py @@ -0,0 +1,621 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Literal + +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 ( + 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 + +__all__ = ["TenantsResource", "AsyncTenantsResource"] + + +class TenantsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> TenantsResourceWithRawResponse: + """ + 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 TenantsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TenantsResourceWithStreamingResponse: + """ + 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 TenantsResourceWithStreamingResponse(self) + + def create( + self, + *, + name: str, + metadata: Optional[Dict[str, Union[str, float, bool, None]]] | 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, + ) -> TenantCreateResponse: + """Create a new tenant. + + Returns the created tenant with a unique `id`. + + Store this ID in your database to + reference this tenant later. + + Args: + name: Display name for the tenant (e.g., your customer's company name) + + metadata: Custom key-value pairs. Useful for storing references to your internal systems. + + **Limits:** + + - Max 50 keys + - Key names max 40 characters + - String values max 500 characters + - Total size max 8KB + + 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( + "/tenants", + body=maybe_transform( + { + "name": name, + "metadata": metadata, + }, + tenant_create_params.TenantCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantCreateResponse, + ) + + def retrieve( + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TenantRetrieveResponse: + """ + Get a tenant by ID. + + 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( + f"/tenants/{tenant_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantRetrieveResponse, + ) + + def update( + self, + tenant_id: str, + *, + metadata: Optional[Dict[str, Union[str, float, bool, None]]] | Omit = omit, + name: str | Omit = omit, + status: Literal["active", "suspended", "archived"] | 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, + ) -> TenantUpdateResponse: + """Update a tenant's name, metadata, or status. + + At least one field is required. + + Metadata is replaced entirely—include all keys you want to keep. + + Args: + metadata: Custom key-value pairs. Useful for storing references to your internal systems. + + **Limits:** + + - Max 50 keys + - Key names max 40 characters + - String values max 500 characters + - Total size max 8KB + + name: Display name for the tenant + + status: Tenant status + + 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}", + body=maybe_transform( + { + "metadata": metadata, + "name": name, + "status": status, + }, + tenant_update_params.TenantUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantUpdateResponse, + ) + + def list( + self, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + status: Literal["active", "suspended", "archived"] | 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[Tenant]: + """List all tenants with pagination. + + Filter by `status` if needed. + + Args: + page: Page number (1-indexed) + + per_page: Number of items per page (max 100) + + status: Filter by tenant status + + 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( + "/tenants", + page=SyncPageNumberPagination[Tenant], + 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, + "status": status, + }, + tenant_list_params.TenantListParams, + ), + ), + model=Tenant, + ) + + def delete( + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TenantDeleteResponse: + """Permanently delete a tenant. + + This 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 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}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantDeleteResponse, + ) + + +class AsyncTenantsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncTenantsResourceWithRawResponse: + """ + 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 AsyncTenantsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTenantsResourceWithStreamingResponse: + """ + 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 AsyncTenantsResourceWithStreamingResponse(self) + + async def create( + self, + *, + name: str, + metadata: Optional[Dict[str, Union[str, float, bool, None]]] | 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, + ) -> TenantCreateResponse: + """Create a new tenant. + + Returns the created tenant with a unique `id`. + + Store this ID in your database to + reference this tenant later. + + Args: + name: Display name for the tenant (e.g., your customer's company name) + + metadata: Custom key-value pairs. Useful for storing references to your internal systems. + + **Limits:** + + - Max 50 keys + - Key names max 40 characters + - String values max 500 characters + - Total size max 8KB + + 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( + "/tenants", + body=await async_maybe_transform( + { + "name": name, + "metadata": metadata, + }, + tenant_create_params.TenantCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantCreateResponse, + ) + + async def retrieve( + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TenantRetrieveResponse: + """ + Get a tenant by ID. + + 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( + f"/tenants/{tenant_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantRetrieveResponse, + ) + + async def update( + self, + tenant_id: str, + *, + metadata: Optional[Dict[str, Union[str, float, bool, None]]] | Omit = omit, + name: str | Omit = omit, + status: Literal["active", "suspended", "archived"] | 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, + ) -> TenantUpdateResponse: + """Update a tenant's name, metadata, or status. + + At least one field is required. + + Metadata is replaced entirely—include all keys you want to keep. + + Args: + metadata: Custom key-value pairs. Useful for storing references to your internal systems. + + **Limits:** + + - Max 50 keys + - Key names max 40 characters + - String values max 500 characters + - Total size max 8KB + + name: Display name for the tenant + + status: Tenant status + + 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}", + body=await async_maybe_transform( + { + "metadata": metadata, + "name": name, + "status": status, + }, + tenant_update_params.TenantUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantUpdateResponse, + ) + + def list( + self, + *, + page: int | Omit = omit, + per_page: int | Omit = omit, + status: Literal["active", "suspended", "archived"] | 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[Tenant, AsyncPageNumberPagination[Tenant]]: + """List all tenants with pagination. + + Filter by `status` if needed. + + Args: + page: Page number (1-indexed) + + per_page: Number of items per page (max 100) + + status: Filter by tenant status + + 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( + "/tenants", + page=AsyncPageNumberPagination[Tenant], + 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, + "status": status, + }, + tenant_list_params.TenantListParams, + ), + ), + model=Tenant, + ) + + async def delete( + 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> TenantDeleteResponse: + """Permanently delete a tenant. + + This 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 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}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TenantDeleteResponse, + ) + + +class TenantsResourceWithRawResponse: + def __init__(self, tenants: TenantsResource) -> None: + self._tenants = tenants + + self.create = to_raw_response_wrapper( + tenants.create, + ) + self.retrieve = to_raw_response_wrapper( + tenants.retrieve, + ) + self.update = to_raw_response_wrapper( + tenants.update, + ) + self.list = to_raw_response_wrapper( + tenants.list, + ) + self.delete = to_raw_response_wrapper( + tenants.delete, + ) + + +class AsyncTenantsResourceWithRawResponse: + def __init__(self, tenants: AsyncTenantsResource) -> None: + self._tenants = tenants + + self.create = async_to_raw_response_wrapper( + tenants.create, + ) + self.retrieve = async_to_raw_response_wrapper( + tenants.retrieve, + ) + self.update = async_to_raw_response_wrapper( + tenants.update, + ) + self.list = async_to_raw_response_wrapper( + tenants.list, + ) + self.delete = async_to_raw_response_wrapper( + tenants.delete, + ) + + +class TenantsResourceWithStreamingResponse: + def __init__(self, tenants: TenantsResource) -> None: + self._tenants = tenants + + self.create = to_streamed_response_wrapper( + tenants.create, + ) + self.retrieve = to_streamed_response_wrapper( + tenants.retrieve, + ) + self.update = to_streamed_response_wrapper( + tenants.update, + ) + self.list = to_streamed_response_wrapper( + tenants.list, + ) + self.delete = to_streamed_response_wrapper( + tenants.delete, + ) + + +class AsyncTenantsResourceWithStreamingResponse: + def __init__(self, tenants: AsyncTenantsResource) -> None: + self._tenants = tenants + + self.create = async_to_streamed_response_wrapper( + tenants.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + tenants.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + tenants.update, + ) + self.list = async_to_streamed_response_wrapper( + tenants.list, + ) + self.delete = async_to_streamed_response_wrapper( + tenants.delete, + ) diff --git a/src/ark/types/__init__.py b/src/ark/types/__init__.py index 6cd75fb..23e9cb2 100644 --- a/src/ark/types/__init__.py +++ b/src/ark/types/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations 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 @@ -10,12 +11,15 @@ 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 .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 .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 @@ -26,6 +30,9 @@ 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 .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 @@ -38,6 +45,7 @@ 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 .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 diff --git a/src/ark/types/email_retrieve_deliveries_response.py b/src/ark/types/email_retrieve_deliveries_response.py index 6406e9e..a591b9d 100644 --- a/src/ark/types/email_retrieve_deliveries_response.py +++ b/src/ark/types/email_retrieve_deliveries_response.py @@ -25,18 +25,67 @@ class DataDelivery(BaseModel): timestamp_iso: datetime = FieldInfo(alias="timestampIso") """ISO 8601 timestamp""" + classification: Optional[ + Literal[ + "invalid_recipient", + "mailbox_full", + "message_too_large", + "spam_block", + "policy_violation", + "no_mailbox", + "not_accepting_mail", + "temporarily_unavailable", + "protocol_error", + "tls_required", + "connection_error", + "dns_error", + "unclassified", + ] + ] = None + """ + Bounce classification category (present for failed deliveries). Helps understand + why delivery failed for analytics and automated handling. + """ + + classification_code: Optional[int] = FieldInfo(alias="classificationCode", default=None) + """ + Numeric bounce classification code for programmatic handling. Codes: + 10=invalid_recipient, 11=no_mailbox, 12=not_accepting_mail, 20=mailbox_full, + 21=message_too_large, 30=spam_block, 31=policy_violation, 32=tls_required, + 40=connection_error, 41=dns_error, 42=temporarily_unavailable, + 50=protocol_error, 99=unclassified + """ + code: Optional[int] = None """SMTP response code""" details: Optional[str] = None - """Status details""" + """Human-readable delivery summary. Format varies by status: + + - **sent**: `Message for {recipient} accepted by {ip}:{port} ({hostname})` + - **softfail/hardfail**: + `{code} {classification}: Delivery to {recipient} failed at {ip}:{port} ({hostname})` + """ output: Optional[str] = None - """SMTP server response from the receiving mail server""" + """Raw SMTP response from the receiving mail server""" + + remote_host: Optional[str] = FieldInfo(alias="remoteHost", default=None) + """ + Hostname of the remote mail server that processed the delivery. Present for all + delivery attempts (successful and failed). + """ sent_with_ssl: Optional[bool] = FieldInfo(alias="sentWithSsl", default=None) """Whether TLS was used""" + smtp_enhanced_code: Optional[str] = FieldInfo(alias="smtpEnhancedCode", default=None) + """ + RFC 3463 enhanced status code from SMTP response (e.g., "5.1.1", "4.2.2"). First + digit: 2=success, 4=temporary, 5=permanent. Second digit: category (1=address, + 2=mailbox, 7=security, etc.). + """ + class DataRetryState(BaseModel): """ diff --git a/src/ark/types/email_retrieve_response.py b/src/ark/types/email_retrieve_response.py index f60801e..d9bc54c 100644 --- a/src/ark/types/email_retrieve_response.py +++ b/src/ark/types/email_retrieve_response.py @@ -93,18 +93,67 @@ class DataDelivery(BaseModel): timestamp_iso: datetime = FieldInfo(alias="timestampIso") """ISO 8601 timestamp""" + classification: Optional[ + Literal[ + "invalid_recipient", + "mailbox_full", + "message_too_large", + "spam_block", + "policy_violation", + "no_mailbox", + "not_accepting_mail", + "temporarily_unavailable", + "protocol_error", + "tls_required", + "connection_error", + "dns_error", + "unclassified", + ] + ] = None + """ + Bounce classification category (present for failed deliveries). Helps understand + why delivery failed for analytics and automated handling. + """ + + classification_code: Optional[int] = FieldInfo(alias="classificationCode", default=None) + """ + Numeric bounce classification code for programmatic handling. Codes: + 10=invalid_recipient, 11=no_mailbox, 12=not_accepting_mail, 20=mailbox_full, + 21=message_too_large, 30=spam_block, 31=policy_violation, 32=tls_required, + 40=connection_error, 41=dns_error, 42=temporarily_unavailable, + 50=protocol_error, 99=unclassified + """ + code: Optional[int] = None """SMTP response code""" details: Optional[str] = None - """Status details""" + """Human-readable delivery summary. Format varies by status: + + - **sent**: `Message for {recipient} accepted by {ip}:{port} ({hostname})` + - **softfail/hardfail**: + `{code} {classification}: Delivery to {recipient} failed at {ip}:{port} ({hostname})` + """ output: Optional[str] = None - """SMTP server response from the receiving mail server""" + """Raw SMTP response from the receiving mail server""" + + remote_host: Optional[str] = FieldInfo(alias="remoteHost", default=None) + """ + Hostname of the remote mail server that processed the delivery. Present for all + delivery attempts (successful and failed). + """ sent_with_ssl: Optional[bool] = FieldInfo(alias="sentWithSsl", default=None) """Whether TLS was used""" + smtp_enhanced_code: Optional[str] = FieldInfo(alias="smtpEnhancedCode", default=None) + """ + RFC 3463 enhanced status code from SMTP response (e.g., "5.1.1", "4.2.2"). First + digit: 2=success, 4=temporary, 5=permanent. Second digit: category (1=address, + 2=mailbox, 7=security, etc.). + """ + class Data(BaseModel): id: str diff --git a/src/ark/types/tenant.py b/src/ark/types/tenant.py new file mode 100644 index 0000000..5c1591a --- /dev/null +++ b/src/ark/types/tenant.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["Tenant"] + + +class Tenant(BaseModel): + id: str + """Unique identifier for the tenant""" + + created_at: datetime + """When the tenant was created""" + + metadata: Dict[str, Union[str, float, bool, None]] + """Custom key-value pairs for storing additional data""" + + name: str + """Display name for the tenant""" + + status: Literal["active", "suspended", "archived"] + """Current status of the tenant: + + - `active` - Normal operation + - `suspended` - Temporarily disabled + - `archived` - Soft-deleted + """ + + updated_at: datetime + """When the tenant was last updated""" diff --git a/src/ark/types/tenant_create_params.py b/src/ark/types/tenant_create_params.py new file mode 100644 index 0000000..9cac09c --- /dev/null +++ b/src/ark/types/tenant_create_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 import Dict, Union, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["TenantCreateParams"] + + +class TenantCreateParams(TypedDict, total=False): + name: Required[str] + """Display name for the tenant (e.g., your customer's company name)""" + + metadata: Optional[Dict[str, Union[str, float, bool, None]]] + """Custom key-value pairs. Useful for storing references to your internal systems. + + **Limits:** + + - Max 50 keys + - Key names max 40 characters + - String values max 500 characters + - Total size max 8KB + """ diff --git a/src/ark/types/tenant_create_response.py b/src/ark/types/tenant_create_response.py new file mode 100644 index 0000000..a44a7a8 --- /dev/null +++ b/src/ark/types/tenant_create_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .tenant import Tenant +from .._models import BaseModel +from .shared.api_meta import APIMeta + +__all__ = ["TenantCreateResponse"] + + +class TenantCreateResponse(BaseModel): + data: Tenant + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenant_delete_response.py b/src/ark/types/tenant_delete_response.py new file mode 100644 index 0000000..f7ba31a --- /dev/null +++ b/src/ark/types/tenant_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__ = ["TenantDeleteResponse", "Data"] + + +class Data(BaseModel): + deleted: Literal[True] + + +class TenantDeleteResponse(BaseModel): + data: Data + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenant_list_params.py b/src/ark/types/tenant_list_params.py new file mode 100644 index 0000000..0c083a9 --- /dev/null +++ b/src/ark/types/tenant_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__ = ["TenantListParams"] + + +class TenantListParams(TypedDict, total=False): + page: int + """Page number (1-indexed)""" + + per_page: Annotated[int, PropertyInfo(alias="perPage")] + """Number of items per page (max 100)""" + + status: Literal["active", "suspended", "archived"] + """Filter by tenant status""" diff --git a/src/ark/types/tenant_retrieve_response.py b/src/ark/types/tenant_retrieve_response.py new file mode 100644 index 0000000..2b5f77d --- /dev/null +++ b/src/ark/types/tenant_retrieve_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .tenant import Tenant +from .._models import BaseModel +from .shared.api_meta import APIMeta + +__all__ = ["TenantRetrieveResponse"] + + +class TenantRetrieveResponse(BaseModel): + data: Tenant + + meta: APIMeta + + success: Literal[True] diff --git a/src/ark/types/tenant_update_params.py b/src/ark/types/tenant_update_params.py new file mode 100644 index 0000000..651209c --- /dev/null +++ b/src/ark/types/tenant_update_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 import Dict, Union, Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["TenantUpdateParams"] + + +class TenantUpdateParams(TypedDict, total=False): + metadata: Optional[Dict[str, Union[str, float, bool, None]]] + """Custom key-value pairs. Useful for storing references to your internal systems. + + **Limits:** + + - Max 50 keys + - Key names max 40 characters + - String values max 500 characters + - Total size max 8KB + """ + + name: str + """Display name for the tenant""" + + status: Literal["active", "suspended", "archived"] + """Tenant status""" diff --git a/src/ark/types/tenant_update_response.py b/src/ark/types/tenant_update_response.py new file mode 100644 index 0000000..8fa7d3e --- /dev/null +++ b/src/ark/types/tenant_update_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .tenant import Tenant +from .._models import BaseModel +from .shared.api_meta import APIMeta + +__all__ = ["TenantUpdateResponse"] + + +class TenantUpdateResponse(BaseModel): + data: Tenant + + meta: APIMeta + + success: Literal[True] diff --git a/tests/api_resources/test_tenants.py b/tests/api_resources/test_tenants.py new file mode 100644 index 0000000..0f02dd2 --- /dev/null +++ b/tests/api_resources/test_tenants.py @@ -0,0 +1,441 @@ +# 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 ( + Tenant, + TenantCreateResponse, + TenantDeleteResponse, + TenantUpdateResponse, + TenantRetrieveResponse, +) +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") + + +class TestTenants: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Ark) -> None: + tenant = client.tenants.create( + name="Acme Corp", + ) + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Ark) -> None: + tenant = client.tenants.create( + name="Acme Corp", + metadata={ + "plan": "pro", + "internal_id": "cust_12345", + "region": "us-west", + }, + ) + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Ark) -> None: + response = client.tenants.with_raw_response.create( + name="Acme Corp", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = response.parse() + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Ark) -> None: + with client.tenants.with_streaming_response.create( + name="Acme Corp", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = response.parse() + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Ark) -> None: + tenant = client.tenants.retrieve( + "cm6abc123def456", + ) + assert_matches_type(TenantRetrieveResponse, tenant, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Ark) -> None: + response = client.tenants.with_raw_response.retrieve( + "cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = response.parse() + assert_matches_type(TenantRetrieveResponse, tenant, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Ark) -> None: + with client.tenants.with_streaming_response.retrieve( + "cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = response.parse() + assert_matches_type(TenantRetrieveResponse, tenant, 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.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_update(self, client: Ark) -> None: + tenant = client.tenants.update( + tenant_id="cm6abc123def456", + ) + assert_matches_type(TenantUpdateResponse, tenant, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Ark) -> None: + tenant = client.tenants.update( + tenant_id="cm6abc123def456", + metadata={ + "plan": "pro", + "internal_id": "cust_12345", + "region": "us-west", + }, + name="Acme Corporation", + status="active", + ) + assert_matches_type(TenantUpdateResponse, tenant, path=["response"]) + + @parametrize + def test_raw_response_update(self, client: Ark) -> None: + response = client.tenants.with_raw_response.update( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = response.parse() + assert_matches_type(TenantUpdateResponse, tenant, path=["response"]) + + @parametrize + def test_streaming_response_update(self, client: Ark) -> None: + with client.tenants.with_streaming_response.update( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = response.parse() + assert_matches_type(TenantUpdateResponse, tenant, 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.with_raw_response.update( + tenant_id="", + ) + + @parametrize + def test_method_list(self, client: Ark) -> None: + tenant = client.tenants.list() + assert_matches_type(SyncPageNumberPagination[Tenant], tenant, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Ark) -> None: + tenant = client.tenants.list( + page=1, + per_page=1, + status="active", + ) + assert_matches_type(SyncPageNumberPagination[Tenant], tenant, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Ark) -> None: + response = client.tenants.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = response.parse() + assert_matches_type(SyncPageNumberPagination[Tenant], tenant, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Ark) -> None: + with client.tenants.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = response.parse() + assert_matches_type(SyncPageNumberPagination[Tenant], tenant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: Ark) -> None: + tenant = client.tenants.delete( + "cm6abc123def456", + ) + assert_matches_type(TenantDeleteResponse, tenant, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Ark) -> None: + response = client.tenants.with_raw_response.delete( + "cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = response.parse() + assert_matches_type(TenantDeleteResponse, tenant, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Ark) -> None: + with client.tenants.with_streaming_response.delete( + "cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = response.parse() + assert_matches_type(TenantDeleteResponse, tenant, 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.with_raw_response.delete( + "", + ) + + +class TestAsyncTenants: + 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: + tenant = await async_client.tenants.create( + name="Acme Corp", + ) + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.create( + name="Acme Corp", + metadata={ + "plan": "pro", + "internal_id": "cust_12345", + "region": "us-west", + }, + ) + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.with_raw_response.create( + name="Acme Corp", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = await response.parse() + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncArk) -> None: + async with async_client.tenants.with_streaming_response.create( + name="Acme Corp", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = await response.parse() + assert_matches_type(TenantCreateResponse, tenant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.retrieve( + "cm6abc123def456", + ) + assert_matches_type(TenantRetrieveResponse, tenant, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.with_raw_response.retrieve( + "cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = await response.parse() + assert_matches_type(TenantRetrieveResponse, tenant, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncArk) -> None: + async with async_client.tenants.with_streaming_response.retrieve( + "cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = await response.parse() + assert_matches_type(TenantRetrieveResponse, tenant, 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.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.update( + tenant_id="cm6abc123def456", + ) + assert_matches_type(TenantUpdateResponse, tenant, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.update( + tenant_id="cm6abc123def456", + metadata={ + "plan": "pro", + "internal_id": "cust_12345", + "region": "us-west", + }, + name="Acme Corporation", + status="active", + ) + assert_matches_type(TenantUpdateResponse, tenant, path=["response"]) + + @parametrize + async def test_raw_response_update(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.with_raw_response.update( + tenant_id="cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = await response.parse() + assert_matches_type(TenantUpdateResponse, tenant, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncArk) -> None: + async with async_client.tenants.with_streaming_response.update( + tenant_id="cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = await response.parse() + assert_matches_type(TenantUpdateResponse, tenant, 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.with_raw_response.update( + tenant_id="", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.list() + assert_matches_type(AsyncPageNumberPagination[Tenant], tenant, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.list( + page=1, + per_page=1, + status="active", + ) + assert_matches_type(AsyncPageNumberPagination[Tenant], tenant, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = await response.parse() + assert_matches_type(AsyncPageNumberPagination[Tenant], tenant, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncArk) -> None: + async with async_client.tenants.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = await response.parse() + assert_matches_type(AsyncPageNumberPagination[Tenant], tenant, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncArk) -> None: + tenant = await async_client.tenants.delete( + "cm6abc123def456", + ) + assert_matches_type(TenantDeleteResponse, tenant, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncArk) -> None: + response = await async_client.tenants.with_raw_response.delete( + "cm6abc123def456", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + tenant = await response.parse() + assert_matches_type(TenantDeleteResponse, tenant, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncArk) -> None: + async with async_client.tenants.with_streaming_response.delete( + "cm6abc123def456", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + tenant = await response.parse() + assert_matches_type(TenantDeleteResponse, tenant, 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.with_raw_response.delete( + "", + )