From 6c2d9dc8f0680358bef61662237c05b1e53f357f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:13:23 +0000 Subject: [PATCH 1/5] chore: rebuild project due to codegen change (#4) --- README.md | 4 +- pyproject.toml | 5 +- requirements-dev.lock | 2 +- src/sent/_compat.py | 6 +- src/sent/_models.py | 9 +- src/sent/_utils/__init__.py | 1 + src/sent/_utils/_transform.py | 9 +- src/sent/_utils/_utils.py | 17 ++ src/sent/resources/contact/__init__.py | 47 ----- src/sent/resources/contact/contact.py | 134 ------------- src/sent/resources/contact/id.py | 165 ---------------- src/sent/resources/contact/phone.py | 165 ---------------- src/sent/resources/contacts.py | 185 ------------------ src/sent/types/contact/__init__.py | 3 - ..._dm_services_contracts_data_contact_dto.py | 40 ---- ...dm_services_contracts_data_customer_dto.py | 36 ---- ...vices_contracts_data_customer_dto_param.py | 37 ---- ...services_contracts_data_sms_payload_dto.py | 30 --- ...ces_contracts_data_whatsapp_payload_dto.py | 36 ---- ...s_contracts_responses_template_response.py | 30 --- ..._dm_services_contracts_data_contact_dto.py | 40 ---- tests/api_resources/contact/__init__.py | 1 - tests/api_resources/contact/test_id.py | 118 ----------- tests/api_resources/contact/test_phone.py | 118 ----------- tests/test_client.py | 4 +- tests/test_models.py | 21 +- tests/test_transform.py | 15 ++ 27 files changed, 64 insertions(+), 1214 deletions(-) delete mode 100644 src/sent/resources/contact/__init__.py delete mode 100644 src/sent/resources/contact/contact.py delete mode 100644 src/sent/resources/contact/id.py delete mode 100644 src/sent/resources/contact/phone.py delete mode 100644 src/sent/resources/contacts.py delete mode 100644 src/sent/types/contact/__init__.py delete mode 100644 src/sent/types/sent_dm_services_contracts_data_contact_dto.py delete mode 100644 src/sent/types/sent_dm_services_contracts_data_customer_dto.py delete mode 100644 src/sent/types/sent_dm_services_contracts_data_customer_dto_param.py delete mode 100644 src/sent/types/sent_dm_services_contracts_data_sms_payload_dto.py delete mode 100644 src/sent/types/sent_dm_services_contracts_data_whatsapp_payload_dto.py delete mode 100644 src/sent/types/sent_dm_services_contracts_responses_template_response.py delete mode 100644 src/sent/types/shared/sent_dm_services_contracts_data_contact_dto.py delete mode 100644 tests/api_resources/contact/__init__.py delete mode 100644 tests/api_resources/contact/test_id.py delete mode 100644 tests/api_resources/contact/test_phone.py diff --git a/README.md b/README.md index 5ce7ccd..b35620b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PyPI version](https://img.shields.io/pypi/v/sentdm.svg)](https://pypi.org/project/sentdm/) -The Sent Python library provides convenient access to the Sent REST API from any Python 3.7+ +The Sent Python library provides convenient access to the Sent REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). @@ -304,7 +304,7 @@ print(sent.__version__) ## Requirements -Python 3.7 or higher. +Python 3.8 or higher. ## Contributing diff --git a/pyproject.toml b/pyproject.toml index cb8f1f5..f695fab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,11 +16,10 @@ dependencies = [ "sniffio", "cached-property; python_version < '3.8'", ] -requires-python = ">= 3.7" +requires-python = ">= 3.8" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -139,7 +138,7 @@ filterwarnings = [ # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.7" +pythonVersion = "3.8" exclude = [ "_dev", diff --git a/requirements-dev.lock b/requirements-dev.lock index 58ff9d1..4d68438 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,7 +48,7 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.11.2 +mypy==1.13.0 mypy-extensions==1.0.0 # via mypy nodeenv==1.8.0 diff --git a/src/sent/_compat.py b/src/sent/_compat.py index d89920d..4794129 100644 --- a/src/sent/_compat.py +++ b/src/sent/_compat.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload from datetime import date, datetime -from typing_extensions import Self +from typing_extensions import Self, Literal import pydantic from pydantic.fields import FieldInfo @@ -137,9 +137,11 @@ def model_dump( exclude_unset: bool = False, exclude_defaults: bool = False, warnings: bool = True, + mode: Literal["json", "python"] = "python", ) -> dict[str, Any]: - if PYDANTIC_V2: + if PYDANTIC_V2 or hasattr(model, "model_dump"): return model.model_dump( + mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, diff --git a/src/sent/_models.py b/src/sent/_models.py index 42551b7..6cb469e 100644 --- a/src/sent/_models.py +++ b/src/sent/_models.py @@ -37,6 +37,7 @@ PropertyInfo, is_list, is_given, + json_safe, lru_cache, is_mapping, parse_date, @@ -279,8 +280,8 @@ def model_dump( Returns: A dictionary representation of the model. """ - if mode != "python": - raise ValueError("mode is only supported in Pydantic v2") + if mode not in {"json", "python"}: + raise ValueError("mode must be either 'json' or 'python'") if round_trip != False: raise ValueError("round_trip is only supported in Pydantic v2") if warnings != True: @@ -289,7 +290,7 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") - return super().dict( # pyright: ignore[reportDeprecated] + dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, by_alias=by_alias, @@ -298,6 +299,8 @@ def model_dump( exclude_none=exclude_none, ) + return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + @override def model_dump_json( self, diff --git a/src/sent/_utils/__init__.py b/src/sent/_utils/__init__.py index 3efe66c..a7cff3c 100644 --- a/src/sent/_utils/__init__.py +++ b/src/sent/_utils/__init__.py @@ -6,6 +6,7 @@ is_list as is_list, is_given as is_given, is_tuple as is_tuple, + json_safe as json_safe, lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, diff --git a/src/sent/_utils/_transform.py b/src/sent/_utils/_transform.py index 47e262a..d7c0534 100644 --- a/src/sent/_utils/_transform.py +++ b/src/sent/_utils/_transform.py @@ -173,6 +173,11 @@ def _transform_recursive( # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] @@ -186,7 +191,7 @@ def _transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) + return model_dump(data, exclude_unset=True, mode="json") annotated_type = _get_annotated_type(annotation) if annotated_type is None: @@ -324,7 +329,7 @@ async def _async_transform_recursive( return data if isinstance(data, pydantic.BaseModel): - return model_dump(data, exclude_unset=True) + return model_dump(data, exclude_unset=True, mode="json") annotated_type = _get_annotated_type(annotation) if annotated_type is None: diff --git a/src/sent/_utils/_utils.py b/src/sent/_utils/_utils.py index 0bba17c..e5811bb 100644 --- a/src/sent/_utils/_utils.py +++ b/src/sent/_utils/_utils.py @@ -16,6 +16,7 @@ overload, ) from pathlib import Path +from datetime import date, datetime from typing_extensions import TypeGuard import sniffio @@ -395,3 +396,19 @@ def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]: maxsize=maxsize, ) return cast(Any, wrapper) # type: ignore[no-any-return] + + +def json_safe(data: object) -> object: + """Translates a mapping / sequence recursively in the same fashion + as `pydantic` v2's `model_dump(mode="json")`. + """ + if is_mapping(data): + return {json_safe(key): json_safe(value) for key, value in data.items()} + + if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)): + return [json_safe(item) for item in data] + + if isinstance(data, (datetime, date)): + return data.isoformat() + + return data diff --git a/src/sent/resources/contact/__init__.py b/src/sent/resources/contact/__init__.py deleted file mode 100644 index 2bc9b35..0000000 --- a/src/sent/resources/contact/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .id import ( - IDResource, - AsyncIDResource, - IDResourceWithRawResponse, - AsyncIDResourceWithRawResponse, - IDResourceWithStreamingResponse, - AsyncIDResourceWithStreamingResponse, -) -from .phone import ( - PhoneResource, - AsyncPhoneResource, - PhoneResourceWithRawResponse, - AsyncPhoneResourceWithRawResponse, - PhoneResourceWithStreamingResponse, - AsyncPhoneResourceWithStreamingResponse, -) -from .contact import ( - ContactResource, - AsyncContactResource, - ContactResourceWithRawResponse, - AsyncContactResourceWithRawResponse, - ContactResourceWithStreamingResponse, - AsyncContactResourceWithStreamingResponse, -) - -__all__ = [ - "IDResource", - "AsyncIDResource", - "IDResourceWithRawResponse", - "AsyncIDResourceWithRawResponse", - "IDResourceWithStreamingResponse", - "AsyncIDResourceWithStreamingResponse", - "PhoneResource", - "AsyncPhoneResource", - "PhoneResourceWithRawResponse", - "AsyncPhoneResourceWithRawResponse", - "PhoneResourceWithStreamingResponse", - "AsyncPhoneResourceWithStreamingResponse", - "ContactResource", - "AsyncContactResource", - "ContactResourceWithRawResponse", - "AsyncContactResourceWithRawResponse", - "ContactResourceWithStreamingResponse", - "AsyncContactResourceWithStreamingResponse", -] diff --git a/src/sent/resources/contact/contact.py b/src/sent/resources/contact/contact.py deleted file mode 100644 index 5b70049..0000000 --- a/src/sent/resources/contact/contact.py +++ /dev/null @@ -1,134 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from .id import ( - IDResource, - AsyncIDResource, - IDResourceWithRawResponse, - AsyncIDResourceWithRawResponse, - IDResourceWithStreamingResponse, - AsyncIDResourceWithStreamingResponse, -) -from .phone import ( - PhoneResource, - AsyncPhoneResource, - PhoneResourceWithRawResponse, - AsyncPhoneResourceWithRawResponse, - PhoneResourceWithStreamingResponse, - AsyncPhoneResourceWithStreamingResponse, -) -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource - -__all__ = ["ContactResource", "AsyncContactResource"] - - -class ContactResource(SyncAPIResource): - @cached_property - def id(self) -> IDResource: - return IDResource(self._client) - - @cached_property - def phone(self) -> PhoneResource: - return PhoneResource(self._client) - - @cached_property - def with_raw_response(self) -> ContactResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return ContactResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ContactResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return ContactResourceWithStreamingResponse(self) - - -class AsyncContactResource(AsyncAPIResource): - @cached_property - def id(self) -> AsyncIDResource: - return AsyncIDResource(self._client) - - @cached_property - def phone(self) -> AsyncPhoneResource: - return AsyncPhoneResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncContactResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return AsyncContactResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncContactResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return AsyncContactResourceWithStreamingResponse(self) - - -class ContactResourceWithRawResponse: - def __init__(self, contact: ContactResource) -> None: - self._contact = contact - - @cached_property - def id(self) -> IDResourceWithRawResponse: - return IDResourceWithRawResponse(self._contact.id) - - @cached_property - def phone(self) -> PhoneResourceWithRawResponse: - return PhoneResourceWithRawResponse(self._contact.phone) - - -class AsyncContactResourceWithRawResponse: - def __init__(self, contact: AsyncContactResource) -> None: - self._contact = contact - - @cached_property - def id(self) -> AsyncIDResourceWithRawResponse: - return AsyncIDResourceWithRawResponse(self._contact.id) - - @cached_property - def phone(self) -> AsyncPhoneResourceWithRawResponse: - return AsyncPhoneResourceWithRawResponse(self._contact.phone) - - -class ContactResourceWithStreamingResponse: - def __init__(self, contact: ContactResource) -> None: - self._contact = contact - - @cached_property - def id(self) -> IDResourceWithStreamingResponse: - return IDResourceWithStreamingResponse(self._contact.id) - - @cached_property - def phone(self) -> PhoneResourceWithStreamingResponse: - return PhoneResourceWithStreamingResponse(self._contact.phone) - - -class AsyncContactResourceWithStreamingResponse: - def __init__(self, contact: AsyncContactResource) -> None: - self._contact = contact - - @cached_property - def id(self) -> AsyncIDResourceWithStreamingResponse: - return AsyncIDResourceWithStreamingResponse(self._contact.id) - - @cached_property - def phone(self) -> AsyncPhoneResourceWithStreamingResponse: - return AsyncPhoneResourceWithStreamingResponse(self._contact.phone) diff --git a/src/sent/resources/contact/id.py b/src/sent/resources/contact/id.py deleted file mode 100644 index 6b85fb2..0000000 --- a/src/sent/resources/contact/id.py +++ /dev/null @@ -1,165 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -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.shared.sent_dm_services_contracts_data_contact_dto import SentDmServicesContractsDataContactDto - -__all__ = ["IDResource", "AsyncIDResource"] - - -class IDResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> IDResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return IDResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> IDResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return IDResourceWithStreamingResponse(self) - - def retrieve( - self, - id: str, - *, - customer_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, - ) -> SentDmServicesContractsDataContactDto: - """ - 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 customer_id: - raise ValueError(f"Expected a non-empty value for `customer_id` but received {customer_id!r}") - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( - f"/contact/{customer_id}/id/{id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SentDmServicesContractsDataContactDto, - ) - - -class AsyncIDResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncIDResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return AsyncIDResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncIDResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return AsyncIDResourceWithStreamingResponse(self) - - async def retrieve( - self, - id: str, - *, - customer_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, - ) -> SentDmServicesContractsDataContactDto: - """ - 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 customer_id: - raise ValueError(f"Expected a non-empty value for `customer_id` but received {customer_id!r}") - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( - f"/contact/{customer_id}/id/{id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SentDmServicesContractsDataContactDto, - ) - - -class IDResourceWithRawResponse: - def __init__(self, id: IDResource) -> None: - self._id = id - - self.retrieve = to_raw_response_wrapper( - id.retrieve, - ) - - -class AsyncIDResourceWithRawResponse: - def __init__(self, id: AsyncIDResource) -> None: - self._id = id - - self.retrieve = async_to_raw_response_wrapper( - id.retrieve, - ) - - -class IDResourceWithStreamingResponse: - def __init__(self, id: IDResource) -> None: - self._id = id - - self.retrieve = to_streamed_response_wrapper( - id.retrieve, - ) - - -class AsyncIDResourceWithStreamingResponse: - def __init__(self, id: AsyncIDResource) -> None: - self._id = id - - self.retrieve = async_to_streamed_response_wrapper( - id.retrieve, - ) diff --git a/src/sent/resources/contact/phone.py b/src/sent/resources/contact/phone.py deleted file mode 100644 index 0b5570f..0000000 --- a/src/sent/resources/contact/phone.py +++ /dev/null @@ -1,165 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -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.shared.sent_dm_services_contracts_data_contact_dto import SentDmServicesContractsDataContactDto - -__all__ = ["PhoneResource", "AsyncPhoneResource"] - - -class PhoneResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> PhoneResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return PhoneResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> PhoneResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return PhoneResourceWithStreamingResponse(self) - - def retrieve( - self, - phone_number: str, - *, - customer_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, - ) -> SentDmServicesContractsDataContactDto: - """ - 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 customer_id: - raise ValueError(f"Expected a non-empty value for `customer_id` but received {customer_id!r}") - if not phone_number: - raise ValueError(f"Expected a non-empty value for `phone_number` but received {phone_number!r}") - return self._get( - f"/contact/{customer_id}/phone/{phone_number}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SentDmServicesContractsDataContactDto, - ) - - -class AsyncPhoneResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncPhoneResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return AsyncPhoneResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncPhoneResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return AsyncPhoneResourceWithStreamingResponse(self) - - async def retrieve( - self, - phone_number: str, - *, - customer_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, - ) -> SentDmServicesContractsDataContactDto: - """ - 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 customer_id: - raise ValueError(f"Expected a non-empty value for `customer_id` but received {customer_id!r}") - if not phone_number: - raise ValueError(f"Expected a non-empty value for `phone_number` but received {phone_number!r}") - return await self._get( - f"/contact/{customer_id}/phone/{phone_number}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SentDmServicesContractsDataContactDto, - ) - - -class PhoneResourceWithRawResponse: - def __init__(self, phone: PhoneResource) -> None: - self._phone = phone - - self.retrieve = to_raw_response_wrapper( - phone.retrieve, - ) - - -class AsyncPhoneResourceWithRawResponse: - def __init__(self, phone: AsyncPhoneResource) -> None: - self._phone = phone - - self.retrieve = async_to_raw_response_wrapper( - phone.retrieve, - ) - - -class PhoneResourceWithStreamingResponse: - def __init__(self, phone: PhoneResource) -> None: - self._phone = phone - - self.retrieve = to_streamed_response_wrapper( - phone.retrieve, - ) - - -class AsyncPhoneResourceWithStreamingResponse: - def __init__(self, phone: AsyncPhoneResource) -> None: - self._phone = phone - - self.retrieve = async_to_streamed_response_wrapper( - phone.retrieve, - ) diff --git a/src/sent/resources/contacts.py b/src/sent/resources/contacts.py deleted file mode 100644 index 852f6ec..0000000 --- a/src/sent/resources/contacts.py +++ /dev/null @@ -1,185 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from ..types import contact_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -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 - -__all__ = ["ContactsResource", "AsyncContactsResource"] - - -class ContactsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> ContactsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return ContactsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> ContactsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return ContactsResourceWithStreamingResponse(self) - - def list( - self, - *, - customer_id: str, - page: int, - page_size: int, - # 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, - ) -> object: - """ - 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._get( - "/contacts", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "customer_id": customer_id, - "page": page, - "page_size": page_size, - }, - contact_list_params.ContactListParams, - ), - ), - cast_to=object, - ) - - -class AsyncContactsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncContactsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return the - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/stainless-sdks/sent-python#accessing-raw-response-data-eg-headers - """ - return AsyncContactsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncContactsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/stainless-sdks/sent-python#with_streaming_response - """ - return AsyncContactsResourceWithStreamingResponse(self) - - async def list( - self, - *, - customer_id: str, - page: int, - page_size: int, - # 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, - ) -> object: - """ - 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._get( - "/contacts", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "customer_id": customer_id, - "page": page, - "page_size": page_size, - }, - contact_list_params.ContactListParams, - ), - ), - cast_to=object, - ) - - -class ContactsResourceWithRawResponse: - def __init__(self, contacts: ContactsResource) -> None: - self._contacts = contacts - - self.list = to_raw_response_wrapper( - contacts.list, - ) - - -class AsyncContactsResourceWithRawResponse: - def __init__(self, contacts: AsyncContactsResource) -> None: - self._contacts = contacts - - self.list = async_to_raw_response_wrapper( - contacts.list, - ) - - -class ContactsResourceWithStreamingResponse: - def __init__(self, contacts: ContactsResource) -> None: - self._contacts = contacts - - self.list = to_streamed_response_wrapper( - contacts.list, - ) - - -class AsyncContactsResourceWithStreamingResponse: - def __init__(self, contacts: AsyncContactsResource) -> None: - self._contacts = contacts - - self.list = async_to_streamed_response_wrapper( - contacts.list, - ) diff --git a/src/sent/types/contact/__init__.py b/src/sent/types/contact/__init__.py deleted file mode 100644 index f8ee8b1..0000000 --- a/src/sent/types/contact/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations diff --git a/src/sent/types/sent_dm_services_contracts_data_contact_dto.py b/src/sent/types/sent_dm_services_contracts_data_contact_dto.py deleted file mode 100644 index b890db5..0000000 --- a/src/sent/types/sent_dm_services_contracts_data_contact_dto.py +++ /dev/null @@ -1,40 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel -from .sent_dm_services_contracts_data_sms_payload_dto import SentDmServicesContractsDataSMSPayloadDto -from .sent_dm_services_contracts_data_whatsapp_payload_dto import SentDmServicesContractsDataWhatsappPayloadDto - -__all__ = ["SentDmServicesContractsDataContactDto"] - - -class SentDmServicesContractsDataContactDto(BaseModel): - id: Optional[str] = None - - available_channels: Optional[str] = FieldInfo(alias="availableChannels", default=None) - - country_code: Optional[str] = FieldInfo(alias="countryCode", default=None) - - created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - - customer_id: Optional[str] = FieldInfo(alias="customerId", default=None) - - default_channel: Optional[str] = FieldInfo(alias="defaultChannel", default=None) - - national_format: Optional[str] = FieldInfo(alias="nationalFormat", default=None) - - phone_number: Optional[str] = FieldInfo(alias="phoneNumber", default=None) - - sms_payload_dto: Optional[SentDmServicesContractsDataSMSPayloadDto] = FieldInfo(alias="smsPayloadDTO", default=None) - - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - - verified: Optional[bool] = None - - whatsapp_payload_dto: Optional[SentDmServicesContractsDataWhatsappPayloadDto] = FieldInfo( - alias="whatsappPayloadDTO", default=None - ) diff --git a/src/sent/types/sent_dm_services_contracts_data_customer_dto.py b/src/sent/types/sent_dm_services_contracts_data_customer_dto.py deleted file mode 100644 index 530b0f5..0000000 --- a/src/sent/types/sent_dm_services_contracts_data_customer_dto.py +++ /dev/null @@ -1,36 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["SentDmServicesContractsDataCustomerDto"] - - -class SentDmServicesContractsDataCustomerDto(BaseModel): - id: Optional[str] = None - - created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - - name: Optional[str] = None - - sending_phone_number: Optional[str] = FieldInfo(alias="sendingPhoneNumber", default=None) - - sms_bearer_token: Optional[str] = FieldInfo(alias="smsBearerToken", default=None) - - sms_default_webhook_url: Optional[str] = FieldInfo(alias="smsDefaultWebhookUrl", default=None) - - sms_messaging_profile_id: Optional[str] = FieldInfo(alias="smsMessagingProfileId", default=None) - - sms_webhook_failover_url: Optional[str] = FieldInfo(alias="smsWebhookFailoverUrl", default=None) - - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - - whatsapp_business_associate_id: Optional[str] = FieldInfo(alias="whatsappBusinessAssociateId", default=None) - - whatsapp_phone_number_id: Optional[str] = FieldInfo(alias="whatsappPhoneNumberId", default=None) - - whatsapp_system_user_access_token: Optional[str] = FieldInfo(alias="whatsappSystemUserAccessToken", default=None) diff --git a/src/sent/types/sent_dm_services_contracts_data_customer_dto_param.py b/src/sent/types/sent_dm_services_contracts_data_customer_dto_param.py deleted file mode 100644 index 80f2ded..0000000 --- a/src/sent/types/sent_dm_services_contracts_data_customer_dto_param.py +++ /dev/null @@ -1,37 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["SentDmServicesContractsDataCustomerDtoParam"] - - -class SentDmServicesContractsDataCustomerDtoParam(TypedDict, total=False): - id: str - - created_at: Annotated[Union[str, datetime], PropertyInfo(alias="createdAt", format="iso8601")] - - name: str - - sending_phone_number: Annotated[str, PropertyInfo(alias="sendingPhoneNumber")] - - sms_bearer_token: Annotated[str, PropertyInfo(alias="smsBearerToken")] - - sms_default_webhook_url: Annotated[str, PropertyInfo(alias="smsDefaultWebhookUrl")] - - sms_messaging_profile_id: Annotated[str, PropertyInfo(alias="smsMessagingProfileId")] - - sms_webhook_failover_url: Annotated[str, PropertyInfo(alias="smsWebhookFailoverUrl")] - - updated_at: Annotated[Union[str, datetime], PropertyInfo(alias="updatedAt", format="iso8601")] - - whatsapp_business_associate_id: Annotated[str, PropertyInfo(alias="whatsappBusinessAssociateId")] - - whatsapp_phone_number_id: Annotated[str, PropertyInfo(alias="whatsappPhoneNumberId")] - - whatsapp_system_user_access_token: Annotated[str, PropertyInfo(alias="whatsappSystemUserAccessToken")] diff --git a/src/sent/types/sent_dm_services_contracts_data_sms_payload_dto.py b/src/sent/types/sent_dm_services_contracts_data_sms_payload_dto.py deleted file mode 100644 index 051c01d..0000000 --- a/src/sent/types/sent_dm_services_contracts_data_sms_payload_dto.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["SentDmServicesContractsDataSMSPayloadDto"] - - -class SentDmServicesContractsDataSMSPayloadDto(BaseModel): - id: Optional[str] = None - - country_code: Optional[str] = FieldInfo(alias="countryCode", default=None) - - created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - - national_format: Optional[str] = FieldInfo(alias="nationalFormat", default=None) - - payload: Optional[str] = None - - phone_number: Optional[str] = FieldInfo(alias="phoneNumber", default=None) - - sent_id: Optional[str] = FieldInfo(alias="sentId", default=None) - - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - - valid_number: Optional[bool] = FieldInfo(alias="validNumber", default=None) diff --git a/src/sent/types/sent_dm_services_contracts_data_whatsapp_payload_dto.py b/src/sent/types/sent_dm_services_contracts_data_whatsapp_payload_dto.py deleted file mode 100644 index 51aefe9..0000000 --- a/src/sent/types/sent_dm_services_contracts_data_whatsapp_payload_dto.py +++ /dev/null @@ -1,36 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["SentDmServicesContractsDataWhatsappPayloadDto"] - - -class SentDmServicesContractsDataWhatsappPayloadDto(BaseModel): - id: Optional[str] = None - - country_code: Optional[str] = FieldInfo(alias="countryCode", default=None) - - created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - - invalid_record: Optional[bool] = FieldInfo(alias="invalidRecord", default=None) - - is_business: Optional[bool] = FieldInfo(alias="isBusiness", default=None) - - number_type: Optional[str] = FieldInfo(alias="numberType", default=None) - - payload: Optional[str] = None - - phone_number: Optional[str] = FieldInfo(alias="phoneNumber", default=None) - - profile_picture: Optional[str] = FieldInfo(alias="profilePicture", default=None) - - sent_id: Optional[str] = FieldInfo(alias="sentId", default=None) - - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - - whatsapp_id: Optional[str] = FieldInfo(alias="whatsappId", default=None) diff --git a/src/sent/types/sent_dm_services_contracts_responses_template_response.py b/src/sent/types/sent_dm_services_contracts_responses_template_response.py deleted file mode 100644 index 7982b66..0000000 --- a/src/sent/types/sent_dm_services_contracts_responses_template_response.py +++ /dev/null @@ -1,30 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["SentDmServicesContractsResponsesTemplateResponse"] - - -class SentDmServicesContractsResponsesTemplateResponse(BaseModel): - id: Optional[str] = None - - category: Optional[str] = None - - created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - - customer_id: Optional[str] = FieldInfo(alias="customerId", default=None) - - name: Optional[str] = None - - raw_body: Optional[str] = FieldInfo(alias="rawBody", default=None) - - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - - whatsapp_template_name: Optional[str] = FieldInfo(alias="whatsappTemplateName", default=None) - - whatsapp_template_status: Optional[str] = FieldInfo(alias="whatsappTemplateStatus", default=None) diff --git a/src/sent/types/shared/sent_dm_services_contracts_data_contact_dto.py b/src/sent/types/shared/sent_dm_services_contracts_data_contact_dto.py deleted file mode 100644 index 6b61656..0000000 --- a/src/sent/types/shared/sent_dm_services_contracts_data_contact_dto.py +++ /dev/null @@ -1,40 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel -from ..sent_dm_services_contracts_data_sms_payload_dto import SentDmServicesContractsDataSMSPayloadDto -from ..sent_dm_services_contracts_data_whatsapp_payload_dto import SentDmServicesContractsDataWhatsappPayloadDto - -__all__ = ["SentDmServicesContractsDataContactDto"] - - -class SentDmServicesContractsDataContactDto(BaseModel): - id: Optional[str] = None - - available_channels: Optional[str] = FieldInfo(alias="availableChannels", default=None) - - country_code: Optional[str] = FieldInfo(alias="countryCode", default=None) - - created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) - - customer_id: Optional[str] = FieldInfo(alias="customerId", default=None) - - default_channel: Optional[str] = FieldInfo(alias="defaultChannel", default=None) - - national_format: Optional[str] = FieldInfo(alias="nationalFormat", default=None) - - phone_number: Optional[str] = FieldInfo(alias="phoneNumber", default=None) - - sms_payload_dto: Optional[SentDmServicesContractsDataSMSPayloadDto] = FieldInfo(alias="smsPayloadDTO", default=None) - - updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) - - verified: Optional[bool] = None - - whatsapp_payload_dto: Optional[SentDmServicesContractsDataWhatsappPayloadDto] = FieldInfo( - alias="whatsappPayloadDTO", default=None - ) diff --git a/tests/api_resources/contact/__init__.py b/tests/api_resources/contact/__init__.py deleted file mode 100644 index fd8019a..0000000 --- a/tests/api_resources/contact/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/contact/test_id.py b/tests/api_resources/contact/test_id.py deleted file mode 100644 index 1105c4a..0000000 --- a/tests/api_resources/contact/test_id.py +++ /dev/null @@ -1,118 +0,0 @@ -# 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 sent import Sent, AsyncSent -from tests.utils import assert_matches_type -from sent.types.shared import SentDmServicesContractsDataContactDto - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestID: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_retrieve(self, client: Sent) -> None: - id = client.contact.id.retrieve( - id="id", - customer_id="customerId", - ) - assert_matches_type(SentDmServicesContractsDataContactDto, id, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Sent) -> None: - response = client.contact.id.with_raw_response.retrieve( - id="id", - customer_id="customerId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - id = response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, id, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Sent) -> None: - with client.contact.id.with_streaming_response.retrieve( - id="id", - customer_id="customerId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - id = response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, id, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Sent) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `customer_id` but received ''"): - client.contact.id.with_raw_response.retrieve( - id="id", - customer_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.contact.id.with_raw_response.retrieve( - id="", - customer_id="customerId", - ) - - -class TestAsyncID: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_retrieve(self, async_client: AsyncSent) -> None: - id = await async_client.contact.id.retrieve( - id="id", - customer_id="customerId", - ) - assert_matches_type(SentDmServicesContractsDataContactDto, id, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncSent) -> None: - response = await async_client.contact.id.with_raw_response.retrieve( - id="id", - customer_id="customerId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - id = await response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, id, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncSent) -> None: - async with async_client.contact.id.with_streaming_response.retrieve( - id="id", - customer_id="customerId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - id = await response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, id, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncSent) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `customer_id` but received ''"): - await async_client.contact.id.with_raw_response.retrieve( - id="id", - customer_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.contact.id.with_raw_response.retrieve( - id="", - customer_id="customerId", - ) diff --git a/tests/api_resources/contact/test_phone.py b/tests/api_resources/contact/test_phone.py deleted file mode 100644 index 535019b..0000000 --- a/tests/api_resources/contact/test_phone.py +++ /dev/null @@ -1,118 +0,0 @@ -# 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 sent import Sent, AsyncSent -from tests.utils import assert_matches_type -from sent.types.shared import SentDmServicesContractsDataContactDto - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestPhone: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_retrieve(self, client: Sent) -> None: - phone = client.contact.phone.retrieve( - phone_number="phoneNumber", - customer_id="customerId", - ) - assert_matches_type(SentDmServicesContractsDataContactDto, phone, path=["response"]) - - @parametrize - def test_raw_response_retrieve(self, client: Sent) -> None: - response = client.contact.phone.with_raw_response.retrieve( - phone_number="phoneNumber", - customer_id="customerId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - phone = response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, phone, path=["response"]) - - @parametrize - def test_streaming_response_retrieve(self, client: Sent) -> None: - with client.contact.phone.with_streaming_response.retrieve( - phone_number="phoneNumber", - customer_id="customerId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - phone = response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, phone, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_retrieve(self, client: Sent) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `customer_id` but received ''"): - client.contact.phone.with_raw_response.retrieve( - phone_number="phoneNumber", - customer_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone_number` but received ''"): - client.contact.phone.with_raw_response.retrieve( - phone_number="", - customer_id="customerId", - ) - - -class TestAsyncPhone: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - async def test_method_retrieve(self, async_client: AsyncSent) -> None: - phone = await async_client.contact.phone.retrieve( - phone_number="phoneNumber", - customer_id="customerId", - ) - assert_matches_type(SentDmServicesContractsDataContactDto, phone, path=["response"]) - - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncSent) -> None: - response = await async_client.contact.phone.with_raw_response.retrieve( - phone_number="phoneNumber", - customer_id="customerId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - phone = await response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, phone, path=["response"]) - - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncSent) -> None: - async with async_client.contact.phone.with_streaming_response.retrieve( - phone_number="phoneNumber", - customer_id="customerId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - phone = await response.parse() - assert_matches_type(SentDmServicesContractsDataContactDto, phone, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncSent) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `customer_id` but received ''"): - await async_client.contact.phone.with_raw_response.retrieve( - phone_number="phoneNumber", - customer_id="", - ) - - with pytest.raises(ValueError, match=r"Expected a non-empty value for `phone_number` but received ''"): - await async_client.contact.phone.with_raw_response.retrieve( - phone_number="", - customer_id="customerId", - ) diff --git a/tests/test_client.py b/tests/test_client.py index cd78c3b..d6cc1d1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -670,7 +670,7 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], - [-1100, "", 7.8], # test large number potentially overflowing + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -1423,7 +1423,7 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], - [-1100, "", 7.8], # test large number potentially overflowing + [-1100, "", 8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) diff --git a/tests/test_models.py b/tests/test_models.py index a651f3c..ccc1f24 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -520,19 +520,15 @@ class Model(BaseModel): assert m3.to_dict(exclude_none=True) == {} assert m3.to_dict(exclude_defaults=True) == {} - if PYDANTIC_V2: - - class Model2(BaseModel): - created_at: datetime + class Model2(BaseModel): + created_at: datetime - time_str = "2024-03-21T11:39:01.275859" - m4 = Model2.construct(created_at=time_str) - assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} - assert m4.to_dict(mode="json") == {"created_at": time_str} - else: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.to_dict(mode="json") + time_str = "2024-03-21T11:39:01.275859" + m4 = Model2.construct(created_at=time_str) + assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} + assert m4.to_dict(mode="json") == {"created_at": time_str} + if not PYDANTIC_V2: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -558,9 +554,6 @@ class Model(BaseModel): assert m3.model_dump(exclude_none=True) == {} if not PYDANTIC_V2: - with pytest.raises(ValueError, match="mode is only supported in Pydantic v2"): - m.model_dump(mode="json") - with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) diff --git a/tests/test_transform.py b/tests/test_transform.py index 1128b11..43ff26a 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -177,17 +177,32 @@ class DateDict(TypedDict, total=False): foo: Annotated[date, PropertyInfo(format="iso8601")] +class DatetimeModel(BaseModel): + foo: datetime + + +class DateModel(BaseModel): + foo: Optional[date] + + @parametrize @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") + tz = "Z" if PYDANTIC_V2 else "+00:00" assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] dt = dt.replace(tzinfo=None) assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] + assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] + assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == { + "foo": "2023-02-23" + } # type: ignore[comparison-overlap] @parametrize From 370c08ce3565afed9f7b6bbc61b0b4469c3c01f5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 00:23:51 +0000 Subject: [PATCH 2/5] chore: rebuild project due to codegen change (#6) --- src/sent/_utils/_transform.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/sent/_utils/_transform.py b/src/sent/_utils/_transform.py index d7c0534..a6b62ca 100644 --- a/src/sent/_utils/_transform.py +++ b/src/sent/_utils/_transform.py @@ -316,6 +316,11 @@ async def _async_transform_recursive( # Iterable[T] or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) ): + # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually + # intended as an iterable, so we don't transform it. + if isinstance(data, dict): + return cast(object, data) + inner_type = extract_type_arg(stripped_type, 0) return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] From 19b12318b61d60a33d0fdde4ddae9759f01e7764 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:01:07 +0000 Subject: [PATCH 3/5] chore: rebuild project due to codegen change (#7) --- pyproject.toml | 1 + requirements-dev.lock | 1 + src/sent/_utils/_sync.py | 90 ++++++++++++++++++---------------------- tests/test_client.py | 38 +++++++++++++++++ 4 files changed, 80 insertions(+), 50 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f695fab..3f1562a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", + "nest_asyncio==1.6.0" ] [tool.rye.scripts] diff --git a/requirements-dev.lock b/requirements-dev.lock index 4d68438..f574b10 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -51,6 +51,7 @@ mdurl==0.1.2 mypy==1.13.0 mypy-extensions==1.0.0 # via mypy +nest-asyncio==1.6.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 diff --git a/src/sent/_utils/_sync.py b/src/sent/_utils/_sync.py index d0d8103..8b3aaf2 100644 --- a/src/sent/_utils/_sync.py +++ b/src/sent/_utils/_sync.py @@ -1,56 +1,62 @@ from __future__ import annotations +import sys +import asyncio import functools -from typing import TypeVar, Callable, Awaitable +import contextvars +from typing import Any, TypeVar, Callable, Awaitable from typing_extensions import ParamSpec -import anyio -import anyio.to_thread - -from ._reflection import function_has_argument - T_Retval = TypeVar("T_Retval") T_ParamSpec = ParamSpec("T_ParamSpec") -# copied from `asyncer`, https://github.com/tiangolo/asyncer -def asyncify( - function: Callable[T_ParamSpec, T_Retval], - *, - cancellable: bool = False, - limiter: anyio.CapacityLimiter | None = None, -) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: +if sys.version_info >= (3, 9): + to_thread = asyncio.to_thread +else: + # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread + # for Python 3.8 support + async def to_thread( + func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs + ) -> Any: + """Asynchronously run function *func* in a separate thread. + + Any *args and **kwargs supplied for this function are directly passed + to *func*. Also, the current :class:`contextvars.Context` is propagated, + allowing context variables from the main thread to be accessed in the + separate thread. + + Returns a coroutine that can be awaited to get the eventual result of *func*. + """ + loop = asyncio.events.get_running_loop() + ctx = contextvars.copy_context() + func_call = functools.partial(ctx.run, func, *args, **kwargs) + return await loop.run_in_executor(None, func_call) + + +# inspired by `asyncer`, https://github.com/tiangolo/asyncer +def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments, and that when called, calls the original function - in a worker thread using `anyio.to_thread.run_sync()`. Internally, - `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports - keyword arguments additional to positional arguments and it adds better support for - autocompletion and inline errors for the arguments of the function called and the - return value. - - If the `cancellable` option is enabled and the task waiting for its completion is - cancelled, the thread will still run its course but its return value (or any raised - exception) will be ignored. + positional and keyword arguments. For python version 3.9 and above, it uses + asyncio.to_thread to run the function in a separate thread. For python version + 3.8, it uses locally defined copy of the asyncio.to_thread function which was + introduced in python 3.9. - Use it like this: + Usage: - ```Python - def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: - # Do work - return "Some result" + ```python + def blocking_func(arg1, arg2, kwarg1=None): + # blocking code + return result - result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") - print(result) + result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1) ``` ## Arguments `function`: a blocking regular callable (e.g. a function) - `cancellable`: `True` to allow cancellation of the operation - `limiter`: capacity limiter to use to limit the total amount of threads running - (if omitted, the default limiter is used) ## Return @@ -60,22 +66,6 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: """ async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: - partial_f = functools.partial(function, *args, **kwargs) - - # In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old - # `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid - # surfacing deprecation warnings. - if function_has_argument(anyio.to_thread.run_sync, "abandon_on_cancel"): - return await anyio.to_thread.run_sync( - partial_f, - abandon_on_cancel=cancellable, - limiter=limiter, - ) - - return await anyio.to_thread.run_sync( - partial_f, - cancellable=cancellable, - limiter=limiter, - ) + return await to_thread(function, *args, **kwargs) return wrapper diff --git a/tests/test_client.py b/tests/test_client.py index d6cc1d1..c232b94 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,11 +4,14 @@ import gc import os +import sys import json import asyncio import inspect +import subprocess import tracemalloc from typing import Any, Union, cast +from textwrap import dedent from unittest import mock from typing_extensions import Literal @@ -1545,3 +1548,38 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: response = await client.messages.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" + + def test_get_platform(self) -> None: + # A previous implementation of asyncify could leave threads unterminated when + # used with nest_asyncio. + # + # Since nest_asyncio.apply() is global and cannot be un-applied, this + # test is run in a separate process to avoid affecting other tests. + test_code = dedent(""" + import asyncio + import nest_asyncio + import threading + + from sent._utils import asyncify + from sent._base_client import get_platform + + async def test_main() -> None: + result = await asyncify(get_platform)() + print(result) + for thread in threading.enumerate(): + print(thread.name) + + nest_asyncio.apply() + asyncio.run(test_main()) + """) + with subprocess.Popen( + [sys.executable, "-c", test_code], + text=True, + ) as process: + try: + process.wait(2) + if process.returncode: + raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") + except subprocess.TimeoutExpired as e: + process.kill() + raise AssertionError("calling get_platform using asyncify resulted in a hung process") from e From 8049dc1866e3cffd17a7de420d18f501e2eab41a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:45:30 +0000 Subject: [PATCH 4/5] chore(internal): fix compat model_dump method when warnings are passed (#8) --- src/sent/_compat.py | 3 ++- tests/test_models.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sent/_compat.py b/src/sent/_compat.py index 4794129..df173f8 100644 --- a/src/sent/_compat.py +++ b/src/sent/_compat.py @@ -145,7 +145,8 @@ def model_dump( exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, - warnings=warnings, + # warnings are not supported in Pydantic v1 + warnings=warnings if PYDANTIC_V2 else True, ) return cast( "dict[str, Any]", diff --git a/tests/test_models.py b/tests/test_models.py index ccc1f24..30fe815 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -561,6 +561,14 @@ class Model(BaseModel): m.model_dump(warnings=False) +def test_compat_method_no_error_for_warnings() -> None: + class Model(BaseModel): + foo: Optional[str] + + m = Model(foo="hello") + assert isinstance(model_dump(m, warnings=False), dict) + + def test_to_json() -> None: class Model(BaseModel): foo: Optional[str] = Field(alias="FOO", default=None) From d53dbbe92ab6255b4f82d17f002751c9823eab65 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:45:43 +0000 Subject: [PATCH 5/5] release: 0.1.0-alpha.2 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- src/sent/_version.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ba6c348..f14b480 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.1" + ".": "0.1.0-alpha.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 56740f6..b8aba1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.1.0-alpha.2 (2024-11-22) + +Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sentdm/sent-python/compare/v0.1.0-alpha.1...v0.1.0-alpha.2) + +### Chores + +* **internal:** fix compat model_dump method when warnings are passed ([#8](https://github.com/sentdm/sent-python/issues/8)) ([8049dc1](https://github.com/sentdm/sent-python/commit/8049dc1866e3cffd17a7de420d18f501e2eab41a)) +* rebuild project due to codegen change ([#4](https://github.com/sentdm/sent-python/issues/4)) ([6c2d9dc](https://github.com/sentdm/sent-python/commit/6c2d9dc8f0680358bef61662237c05b1e53f357f)) +* rebuild project due to codegen change ([#6](https://github.com/sentdm/sent-python/issues/6)) ([370c08c](https://github.com/sentdm/sent-python/commit/370c08ce3565afed9f7b6bbc61b0b4469c3c01f5)) +* rebuild project due to codegen change ([#7](https://github.com/sentdm/sent-python/issues/7)) ([19b1231](https://github.com/sentdm/sent-python/commit/19b12318b61d60a33d0fdde4ddae9759f01e7764)) + ## 0.1.0-alpha.1 (2024-10-31) Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sentdm/sent-python/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) diff --git a/pyproject.toml b/pyproject.toml index 3f1562a..7da8a6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sentdm" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" description = "The official Python library for the Sent API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/sent/_version.py b/src/sent/_version.py index 3091cad..abc717a 100644 --- a/src/sent/_version.py +++ b/src/sent/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "sent" -__version__ = "0.1.0-alpha.1" # x-release-please-version +__version__ = "0.1.0-alpha.2" # x-release-please-version