diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index a26ebfc..8f3e0a4 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.14.0"
+ ".": "0.15.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 0976618..69b13d9 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-1949bcfc8775c97eca880428dc93e9f97aa91144bef82584027ede5089bb2e19.yml
-openapi_spec_hash: 0aa367455a067b701f18ef7892b6c7e9
-config_hash: 373e654f8034a40c42234eee9ebefbb9
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/ark%2Fark-39b91ffd46b6e41924f8465ffaaff6ba3c200a68daa513d4f1eb1e4b29aba78f.yml
+openapi_spec_hash: 542dd50007316698c83e8b0bdd5e40e2
+config_hash: 77a3908ee910a8019f5831d3a3d53c18
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aabac85..33dca82 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 0.15.0 (2026-01-30)
+
+Full Changelog: [v0.14.0...v0.15.0](https://github.com/ArkHQ-io/ark-python/compare/v0.14.0...v0.15.0)
+
+### Features
+
+* **api:** api update ([0f5c166](https://github.com/ArkHQ-io/ark-python/commit/0f5c1666d7c02d1f18096610716be6cb2c39a281))
+* **api:** api update ([f128894](https://github.com/ArkHQ-io/ark-python/commit/f128894ef84fffa757424b4b1684f7e4eebf4629))
+* **api:** manual updates ([bcc7230](https://github.com/ArkHQ-io/ark-python/commit/bcc72308bfbfd6bfd6cefc6df9e1019637ce1b02))
+* **api:** manual updates ([378cd65](https://github.com/ArkHQ-io/ark-python/commit/378cd65aadfd41edd36f4d82ad3ea12d95ec8f0c))
+* **api:** manual updates ([edb503c](https://github.com/ArkHQ-io/ark-python/commit/edb503c58439ac4face4260cf1e6794cab79f8bb))
+* **client:** add custom JSON encoder for extended type support ([ff51eb2](https://github.com/ArkHQ-io/ark-python/commit/ff51eb229a2eda74e3bc8c16d5f7f43758dc5c31))
+
## 0.14.0 (2026-01-29)
Full Changelog: [v0.13.0...v0.14.0](https://github.com/ArkHQ-io/ark-python/compare/v0.13.0...v0.14.0)
diff --git a/api.md b/api.md
index d511272..7baaad3 100644
--- a/api.md
+++ b/api.md
@@ -22,10 +22,10 @@ from ark.types import (
Methods:
-- client.emails.retrieve(email_id, \*\*params) -> EmailRetrieveResponse
+- client.emails.retrieve(id, \*\*params) -> EmailRetrieveResponse
- client.emails.list(\*\*params) -> SyncPageNumberPagination[EmailListResponse]
-- client.emails.retrieve_deliveries(email_id) -> EmailRetrieveDeliveriesResponse
-- client.emails.retry(email_id) -> EmailRetryResponse
+- client.emails.retrieve_deliveries(id) -> EmailRetrieveDeliveriesResponse
+- client.emails.retry(id) -> EmailRetryResponse
- client.emails.send(\*\*params) -> EmailSendResponse
- client.emails.send_batch(\*\*params) -> EmailSendBatchResponse
- client.emails.send_raw(\*\*params) -> EmailSendRawResponse
diff --git a/pyproject.toml b/pyproject.toml
index 0f30e15..6c28b5f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "ark-email"
-version = "0.14.0"
+version = "0.15.0"
description = "The official Python library for the ark API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/ark/_base_client.py b/src/ark/_base_client.py
index e3e6436..6fd69b9 100644
--- a/src/ark/_base_client.py
+++ b/src/ark/_base_client.py
@@ -86,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
+from ._utils._json import openapi_dumps
log: logging.Logger = logging.getLogger(__name__)
@@ -554,8 +555,10 @@ def _build_request(
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
- else:
- kwargs["json"] = json_data if is_given(json_data) else None
+ elif not files:
+ # Don't set content when JSON is sent as multipart/form-data,
+ # since httpx's content param overrides other body arguments
+ kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
diff --git a/src/ark/_compat.py b/src/ark/_compat.py
index bdef67f..786ff42 100644
--- a/src/ark/_compat.py
+++ b/src/ark/_compat.py
@@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
+ by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
@@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
+ by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
- exclude=exclude,
- exclude_unset=exclude_unset,
- exclude_defaults=exclude_defaults,
+ exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)
diff --git a/src/ark/_utils/_json.py b/src/ark/_utils/_json.py
new file mode 100644
index 0000000..6058421
--- /dev/null
+++ b/src/ark/_utils/_json.py
@@ -0,0 +1,35 @@
+import json
+from typing import Any
+from datetime import datetime
+from typing_extensions import override
+
+import pydantic
+
+from .._compat import model_dump
+
+
+def openapi_dumps(obj: Any) -> bytes:
+ """
+ Serialize an object to UTF-8 encoded JSON bytes.
+
+ Extends the standard json.dumps with support for additional types
+ commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
+ """
+ return json.dumps(
+ obj,
+ cls=_CustomEncoder,
+ # Uses the same defaults as httpx's JSON serialization
+ ensure_ascii=False,
+ separators=(",", ":"),
+ allow_nan=False,
+ ).encode()
+
+
+class _CustomEncoder(json.JSONEncoder):
+ @override
+ def default(self, o: Any) -> Any:
+ if isinstance(o, datetime):
+ return o.isoformat()
+ if isinstance(o, pydantic.BaseModel):
+ return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
+ return super().default(o)
diff --git a/src/ark/_version.py b/src/ark/_version.py
index 9e3345c..1750a65 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.14.0" # x-release-please-version
+__version__ = "0.15.0" # x-release-please-version
diff --git a/src/ark/resources/emails.py b/src/ark/resources/emails.py
index cd1f533..4169d3a 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,
- email_id: str,
+ 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 email_id:
- raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._get(
- f"/emails/{email_id}",
+ f"/emails/{id}",
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -200,7 +200,7 @@ def list(
def retrieve_deliveries(
self,
- email_id: str,
+ 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/{emailId}/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/{id}/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 email_id:
- raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._get(
- f"/emails/{email_id}/deliveries",
+ f"/emails/{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,
- email_id: str,
+ 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 email_id:
- raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return self._post(
- f"/emails/{email_id}/retry",
+ f"/emails/{id}/retry",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -575,7 +575,7 @@ def with_streaming_response(self) -> AsyncEmailsResourceWithStreamingResponse:
async def retrieve(
self,
- email_id: str,
+ 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 email_id:
- raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._get(
- f"/emails/{email_id}",
+ f"/emails/{id}",
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
@@ -716,7 +716,7 @@ def list(
async def retrieve_deliveries(
self,
- email_id: str,
+ 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/{emailId}/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/{id}/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 email_id:
- raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._get(
- f"/emails/{email_id}/deliveries",
+ f"/emails/{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,
- email_id: str,
+ 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 email_id:
- raise ValueError(f"Expected a non-empty value for `email_id` but received {email_id!r}")
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
return await self._post(
- f"/emails/{email_id}/retry",
+ f"/emails/{id}/retry",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
diff --git a/src/ark/types/email_list_response.py b/src/ark/types/email_list_response.py
index f8a1d1c..dce7d39 100644
--- a/src/ark/types/email_list_response.py
+++ b/src/ark/types/email_list_response.py
@@ -13,9 +13,7 @@
class EmailListResponse(BaseModel):
id: str
- """Internal message ID"""
-
- token: str
+ """Unique message identifier (token)"""
from_: str = FieldInfo(alias="from")
diff --git a/src/ark/types/email_retrieve_deliveries_response.py b/src/ark/types/email_retrieve_deliveries_response.py
index 584bad7..6406e9e 100644
--- a/src/ark/types/email_retrieve_deliveries_response.py
+++ b/src/ark/types/email_retrieve_deliveries_response.py
@@ -88,9 +88,12 @@ class DataRetryState(BaseModel):
class Data(BaseModel):
+ id: str
+ """Message identifier (token)"""
+
can_retry_manually: bool = FieldInfo(alias="canRetryManually")
"""
- Whether the message can be manually retried via `POST /emails/{emailId}/retry`.
+ Whether the message can be manually retried via `POST /emails/{id}/retry`.
`true` when the raw message content is still available (not expired). Messages
older than the retention period cannot be retried.
"""
@@ -101,12 +104,6 @@ class Data(BaseModel):
SMTP response codes and timestamps.
"""
- message_id: int = FieldInfo(alias="messageId")
- """Internal numeric message ID"""
-
- message_token: str = FieldInfo(alias="messageToken")
- """Unique message token for API references"""
-
retry_state: Optional[DataRetryState] = FieldInfo(alias="retryState", default=None)
"""
Information about the current retry state of a message that is queued for
diff --git a/src/ark/types/email_retrieve_response.py b/src/ark/types/email_retrieve_response.py
index b250031..f60801e 100644
--- a/src/ark/types/email_retrieve_response.py
+++ b/src/ark/types/email_retrieve_response.py
@@ -108,14 +108,7 @@ class DataDelivery(BaseModel):
class Data(BaseModel):
id: str
- """Internal message ID"""
-
- token: str
- """
- Unique message token used to retrieve this email via API. Combined with id to
- form the full message identifier: msg*{id}*{token} Use this token with GET
- /emails/{emailId} where emailId = "msg*{id}*{token}"
- """
+ """Unique message identifier (token)"""
from_: str = FieldInfo(alias="from")
"""Sender address"""
diff --git a/src/ark/types/email_retry_response.py b/src/ark/types/email_retry_response.py
index 8c5029b..f63cc99 100644
--- a/src/ark/types/email_retry_response.py
+++ b/src/ark/types/email_retry_response.py
@@ -9,6 +9,9 @@
class Data(BaseModel):
+ id: str
+ """Email identifier (token)"""
+
message: str
diff --git a/src/ark/types/email_send_batch_response.py b/src/ark/types/email_send_batch_response.py
index 272119c..d32e3fc 100644
--- a/src/ark/types/email_send_batch_response.py
+++ b/src/ark/types/email_send_batch_response.py
@@ -11,9 +11,7 @@
class DataMessages(BaseModel):
id: str
- """Message ID"""
-
- token: str
+ """Message identifier (token)"""
class Data(BaseModel):
diff --git a/src/ark/types/email_send_raw_response.py b/src/ark/types/email_send_raw_response.py
index f1e8c03..46e7c96 100644
--- a/src/ark/types/email_send_raw_response.py
+++ b/src/ark/types/email_send_raw_response.py
@@ -13,7 +13,7 @@
class Data(BaseModel):
id: str
- """Unique message ID (format: msg*{id}*{token})"""
+ """Unique message identifier (token)"""
status: Literal["pending", "sent"]
"""Current delivery status"""
diff --git a/src/ark/types/email_send_response.py b/src/ark/types/email_send_response.py
index 39e1d36..cb7d814 100644
--- a/src/ark/types/email_send_response.py
+++ b/src/ark/types/email_send_response.py
@@ -13,7 +13,7 @@
class Data(BaseModel):
id: str
- """Unique message ID (format: msg*{id}*{token})"""
+ """Unique message identifier (token)"""
status: Literal["pending", "sent"]
"""Current delivery status"""
diff --git a/src/ark/types/log_entry.py b/src/ark/types/log_entry.py
index 9fad7e7..4dfdd6c 100644
--- a/src/ark/types/log_entry.py
+++ b/src/ark/types/log_entry.py
@@ -57,7 +57,7 @@ class Email(BaseModel):
"""Email-specific data (for email endpoints)"""
id: Optional[str] = None
- """Email message ID"""
+ """Email message identifier (token)"""
recipient_count: Optional[int] = FieldInfo(alias="recipientCount", default=None)
"""Number of recipients"""
diff --git a/tests/api_resources/test_emails.py b/tests/api_resources/test_emails.py
index 17781ac..7a54df7 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(
- email_id="emailId",
+ 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(
- email_id="emailId",
+ 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(
- email_id="emailId",
+ 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(
- email_id="emailId",
+ 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 `email_id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
client.emails.with_raw_response.retrieve(
- email_id="",
+ id="",
)
@parametrize
@@ -114,14 +114,14 @@ def test_streaming_response_list(self, client: Ark) -> None:
@parametrize
def test_method_retrieve_deliveries(self, client: Ark) -> None:
email = client.emails.retrieve_deliveries(
- "msg_12345_aBc123XyZ",
+ "aBc123XyZ",
)
assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"])
@parametrize
def test_raw_response_retrieve_deliveries(self, client: Ark) -> None:
response = client.emails.with_raw_response.retrieve_deliveries(
- "msg_12345_aBc123XyZ",
+ "aBc123XyZ",
)
assert response.is_closed is True
@@ -132,7 +132,7 @@ def test_raw_response_retrieve_deliveries(self, client: Ark) -> None:
@parametrize
def test_streaming_response_retrieve_deliveries(self, client: Ark) -> None:
with client.emails.with_streaming_response.retrieve_deliveries(
- "msg_12345_aBc123XyZ",
+ "aBc123XyZ",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -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 `email_id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
client.emails.with_raw_response.retrieve_deliveries(
"",
)
@@ -152,14 +152,14 @@ def test_path_params_retrieve_deliveries(self, client: Ark) -> None:
@parametrize
def test_method_retry(self, client: Ark) -> None:
email = client.emails.retry(
- "emailId",
+ "aBc123XyZ",
)
assert_matches_type(EmailRetryResponse, email, path=["response"])
@parametrize
def test_raw_response_retry(self, client: Ark) -> None:
response = client.emails.with_raw_response.retry(
- "emailId",
+ "aBc123XyZ",
)
assert response.is_closed is True
@@ -170,7 +170,7 @@ def test_raw_response_retry(self, client: Ark) -> None:
@parametrize
def test_streaming_response_retry(self, client: Ark) -> None:
with client.emails.with_streaming_response.retry(
- "emailId",
+ "aBc123XyZ",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -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 `email_id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `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(
- email_id="emailId",
+ 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(
- email_id="emailId",
+ 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(
- email_id="emailId",
+ 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(
- email_id="emailId",
+ 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 `email_id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
await async_client.emails.with_raw_response.retrieve(
- email_id="",
+ id="",
)
@parametrize
@@ -488,14 +488,14 @@ async def test_streaming_response_list(self, async_client: AsyncArk) -> None:
@parametrize
async def test_method_retrieve_deliveries(self, async_client: AsyncArk) -> None:
email = await async_client.emails.retrieve_deliveries(
- "msg_12345_aBc123XyZ",
+ "aBc123XyZ",
)
assert_matches_type(EmailRetrieveDeliveriesResponse, email, path=["response"])
@parametrize
async def test_raw_response_retrieve_deliveries(self, async_client: AsyncArk) -> None:
response = await async_client.emails.with_raw_response.retrieve_deliveries(
- "msg_12345_aBc123XyZ",
+ "aBc123XyZ",
)
assert response.is_closed is True
@@ -506,7 +506,7 @@ async def test_raw_response_retrieve_deliveries(self, async_client: AsyncArk) ->
@parametrize
async def test_streaming_response_retrieve_deliveries(self, async_client: AsyncArk) -> None:
async with async_client.emails.with_streaming_response.retrieve_deliveries(
- "msg_12345_aBc123XyZ",
+ "aBc123XyZ",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -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 `email_id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
await async_client.emails.with_raw_response.retrieve_deliveries(
"",
)
@@ -526,14 +526,14 @@ async def test_path_params_retrieve_deliveries(self, async_client: AsyncArk) ->
@parametrize
async def test_method_retry(self, async_client: AsyncArk) -> None:
email = await async_client.emails.retry(
- "emailId",
+ "aBc123XyZ",
)
assert_matches_type(EmailRetryResponse, email, path=["response"])
@parametrize
async def test_raw_response_retry(self, async_client: AsyncArk) -> None:
response = await async_client.emails.with_raw_response.retry(
- "emailId",
+ "aBc123XyZ",
)
assert response.is_closed is True
@@ -544,7 +544,7 @@ async def test_raw_response_retry(self, async_client: AsyncArk) -> None:
@parametrize
async def test_streaming_response_retry(self, async_client: AsyncArk) -> None:
async with async_client.emails.with_streaming_response.retry(
- "emailId",
+ "aBc123XyZ",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -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 `email_id` but received ''"):
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
await async_client.emails.with_raw_response.retry(
"",
)
diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py
new file mode 100644
index 0000000..7837132
--- /dev/null
+++ b/tests/test_utils/test_json.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import datetime
+from typing import Union
+
+import pydantic
+
+from ark import _compat
+from ark._utils._json import openapi_dumps
+
+
+class TestOpenapiDumps:
+ def test_basic(self) -> None:
+ data = {"key": "value", "number": 42}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"key":"value","number":42}'
+
+ def test_datetime_serialization(self) -> None:
+ dt = datetime.datetime(2023, 1, 1, 12, 0, 0)
+ data = {"datetime": dt}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}'
+
+ def test_pydantic_model_serialization(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str
+ last_name: str
+ age: int
+
+ model_instance = User(first_name="John", last_name="Kramer", age=83)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}'
+
+ def test_pydantic_model_with_default_values(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+ score: int = 0
+
+ model_instance = User(name="Alice")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Alice"}}'
+
+ def test_pydantic_model_with_default_values_overridden(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+
+ model_instance = User(name="Bob", role="admin", active=False)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}'
+
+ def test_pydantic_model_with_alias(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str = pydantic.Field(alias="firstName")
+ last_name: str = pydantic.Field(alias="lastName")
+
+ model_instance = User(firstName="John", lastName="Doe")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}'
+
+ def test_pydantic_model_with_alias_and_default(self) -> None:
+ class User(pydantic.BaseModel):
+ user_name: str = pydantic.Field(alias="userName")
+ user_role: str = pydantic.Field(default="member", alias="userRole")
+ is_active: bool = pydantic.Field(default=True, alias="isActive")
+
+ model_instance = User(userName="charlie")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"charlie"}}'
+
+ model_with_overrides = User(userName="diana", userRole="admin", isActive=False)
+ data = {"model": model_with_overrides}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}'
+
+ def test_pydantic_model_with_nested_models_and_defaults(self) -> None:
+ class Address(pydantic.BaseModel):
+ street: str
+ city: str = "Unknown"
+
+ class User(pydantic.BaseModel):
+ name: str
+ address: Address
+ verified: bool = False
+
+ if _compat.PYDANTIC_V1:
+ # to handle forward references in Pydantic v1
+ User.update_forward_refs(**locals()) # type: ignore[reportDeprecated]
+
+ address = Address(street="123 Main St")
+ user = User(name="Diana", address=address)
+ data = {"user": user}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}'
+
+ address_with_city = Address(street="456 Oak Ave", city="Boston")
+ user_verified = User(name="Eve", address=address_with_city, verified=True)
+ data = {"user": user_verified}
+ json_bytes = openapi_dumps(data)
+ assert (
+ json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}'
+ )
+
+ def test_pydantic_model_with_optional_fields(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ email: Union[str, None]
+ phone: Union[str, None]
+
+ model_with_none = User(name="Eve", email=None, phone=None)
+ data = {"model": model_with_none}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}'
+
+ model_with_values = User(name="Frank", email="frank@example.com", phone=None)
+ data = {"model": model_with_values}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'