diff --git a/.spec-sha256 b/.spec-sha256 index c86c3d8..903fef4 100644 --- a/.spec-sha256 +++ b/.spec-sha256 @@ -1 +1 @@ -1c648bd5dd3dab534723d1cb26e19fdbfcc7359cd8a33a1f8d99e5688e4ba54f +8c6a3b8bfecad67daa21d1361057c24f2e063b17c13afdcc93e7f71806011003 diff --git a/src/controlresell/client.py b/src/controlresell/client.py index ee2b322..7aeff5d 100644 --- a/src/controlresell/client.py +++ b/src/controlresell/client.py @@ -33,6 +33,7 @@ republish_item, update_item, ) +from .generated.api.customers import get_customer, list_customers from .generated.api.me import get_me from .generated.api.orders import get_order, list_labels_to_ship, list_order_labels, list_orders from .generated.client import AuthenticatedClient @@ -41,6 +42,8 @@ AccountPage, Conversation, ConversationPage, + Customer, + CustomerPage, Item, ItemCreateRequest, ItemPage, @@ -109,6 +112,7 @@ def __init__( self.items = _Items(self) self.conversations = _Conversations(self) self.orders = _Orders(self) + self.customers = _Customers(self) @property def http(self) -> httpx.Client: @@ -386,3 +390,43 @@ def iter( limit=limit, cursor=cursor, bucket=bucket, platform=platform, account_id=account_id, from_=from_, to=to ) ) + + +class _Customers: + """The other parties of the seller's sales and purchases (needs the ``customers:read`` scope).""" + + def __init__(self, sdk: ControlResell) -> None: + self._sdk = sdk + + def list( + self, + *, + limit: int | None = None, + cursor: str | None = None, + account_id: UUID | str | None = None, + from_: datetime.datetime | None = None, + to: datetime.datetime | None = None, + ) -> CustomerPage: + """One page of customers, the most recently created first. + + ``from_`` is inclusive and ``to`` exclusive, on the creation date (a naive datetime is read as UTC). + """ + account = UUID(str(account_id)) if account_id is not None else None + return self._sdk._call( # noqa: SLF001 + list_customers, **_query(limit=limit, cursor=cursor, account_id=account, from_=_utc(from_), to=_utc(to)) + ) + + def get(self, id: UUID | str) -> Customer: + """One customer by its identifier.""" + return self._sdk._call(get_customer, id=UUID(str(id))) # noqa: SLF001 + + def iter( + self, + *, + limit: int | None = None, + account_id: UUID | str | None = None, + from_: datetime.datetime | None = None, + to: datetime.datetime | None = None, + ) -> Iterator[Customer]: + """Every customer, page after page, with the same filters.""" + return paginate(lambda cursor: self.list(limit=limit, cursor=cursor, account_id=account_id, from_=from_, to=to)) diff --git a/src/controlresell/generated/api/customers/__init__.py b/src/controlresell/generated/api/customers/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/src/controlresell/generated/api/customers/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/src/controlresell/generated/api/customers/get_customer.py b/src/controlresell/generated/api/customers/get_customer.py new file mode 100644 index 0000000..0201640 --- /dev/null +++ b/src/controlresell/generated/api/customers/get_customer.py @@ -0,0 +1,202 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote +from uuid import UUID + +import httpx + +from ...client import AuthenticatedClient, Client +from ...models.customer import Customer +from ...models.error_response import ErrorResponse +from ...types import Response + + +def _get_kwargs( + id: UUID, +) -> dict[str, Any]: + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/api/public/v1/customers/{id}".format( + id=quote(str(id), safe=""), + ), + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Customer | ErrorResponse: + if response.status_code == 200: + response_200 = Customer.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 402: + response_402 = ErrorResponse.from_dict(response.json()) + + return response_402 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + response_default = ErrorResponse.from_dict(response.json()) + + return response_default + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Customer | ErrorResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + id: UUID, + *, + client: AuthenticatedClient | Client, +) -> Response[Customer | ErrorResponse]: + """Read one customer + + One customer of the seller, by its identifier (`GET /customers` lists them). Requires + `customers:read`. + + Args: + id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Customer | ErrorResponse] + """ + + kwargs = _get_kwargs( + id=id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + id: UUID, + *, + client: AuthenticatedClient | Client, +) -> Customer | ErrorResponse | None: + """Read one customer + + One customer of the seller, by its identifier (`GET /customers` lists them). Requires + `customers:read`. + + Args: + id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Customer | ErrorResponse + """ + + return sync_detailed( + id=id, + client=client, + ).parsed + + +async def asyncio_detailed( + id: UUID, + *, + client: AuthenticatedClient | Client, +) -> Response[Customer | ErrorResponse]: + """Read one customer + + One customer of the seller, by its identifier (`GET /customers` lists them). Requires + `customers:read`. + + Args: + id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Customer | ErrorResponse] + """ + + kwargs = _get_kwargs( + id=id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + id: UUID, + *, + client: AuthenticatedClient | Client, +) -> Customer | ErrorResponse | None: + """Read one customer + + One customer of the seller, by its identifier (`GET /customers` lists them). Requires + `customers:read`. + + Args: + id (UUID): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Customer | ErrorResponse + """ + + return ( + await asyncio_detailed( + id=id, + client=client, + ) + ).parsed diff --git a/src/controlresell/generated/api/customers/list_customers.py b/src/controlresell/generated/api/customers/list_customers.py new file mode 100644 index 0000000..97248d8 --- /dev/null +++ b/src/controlresell/generated/api/customers/list_customers.py @@ -0,0 +1,284 @@ +import datetime +from http import HTTPStatus +from typing import Any +from uuid import UUID + +import httpx + +from ...client import AuthenticatedClient, Client +from ...models.customer_page import CustomerPage +from ...models.error_response import ErrorResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + *, + limit: int | Unset = 50, + cursor: str | Unset = UNSET, + from_: datetime.datetime | Unset = UNSET, + to: datetime.datetime | Unset = UNSET, + account_id: UUID | Unset = UNSET, +) -> dict[str, Any]: + + params: dict[str, Any] = {} + + params["limit"] = limit + + params["cursor"] = cursor + + json_from_: str | Unset = UNSET + if not isinstance(from_, Unset): + json_from_ = from_.isoformat() + params["from"] = json_from_ + + json_to: str | Unset = UNSET + if not isinstance(to, Unset): + json_to = to.isoformat() + params["to"] = json_to + + json_account_id: str | Unset = UNSET + if not isinstance(account_id, Unset): + json_account_id = str(account_id) + params["accountId"] = json_account_id + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/api/public/v1/customers", + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> CustomerPage | ErrorResponse: + if response.status_code == 200: + response_200 = CustomerPage.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 402: + response_402 = ErrorResponse.from_dict(response.json()) + + return response_402 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + response_default = ErrorResponse.from_dict(response.json()) + + return response_default + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[CustomerPage | ErrorResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + cursor: str | Unset = UNSET, + from_: datetime.datetime | Unset = UNSET, + to: datetime.datetime | Unset = UNSET, + account_id: UUID | Unset = UNSET, +) -> Response[CustomerPage | ErrorResponse]: + """List the seller's customers + + The other parties of the seller's sales and purchases, as written on their invoices: a customer is + created with the invoice of its first order. The most recently created first (by `createdAt`, then + `id`), one page at a time: pass the `nextCursor` of a page as `cursor` to read the next one, with + the same filters (a cursor reused with other filters is an `invalid_cursor`). `from` and `to` go + straight to a period of creation: `from` is inclusive, `to` exclusive. Requires `customers:read`. + + Args: + limit (int | Unset): Default: 50. + cursor (str | Unset): + from_ (datetime.datetime | Unset): + to (datetime.datetime | Unset): + account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[CustomerPage | ErrorResponse] + """ + + kwargs = _get_kwargs( + limit=limit, + cursor=cursor, + from_=from_, + to=to, + account_id=account_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + cursor: str | Unset = UNSET, + from_: datetime.datetime | Unset = UNSET, + to: datetime.datetime | Unset = UNSET, + account_id: UUID | Unset = UNSET, +) -> CustomerPage | ErrorResponse | None: + """List the seller's customers + + The other parties of the seller's sales and purchases, as written on their invoices: a customer is + created with the invoice of its first order. The most recently created first (by `createdAt`, then + `id`), one page at a time: pass the `nextCursor` of a page as `cursor` to read the next one, with + the same filters (a cursor reused with other filters is an `invalid_cursor`). `from` and `to` go + straight to a period of creation: `from` is inclusive, `to` exclusive. Requires `customers:read`. + + Args: + limit (int | Unset): Default: 50. + cursor (str | Unset): + from_ (datetime.datetime | Unset): + to (datetime.datetime | Unset): + account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + CustomerPage | ErrorResponse + """ + + return sync_detailed( + client=client, + limit=limit, + cursor=cursor, + from_=from_, + to=to, + account_id=account_id, + ).parsed + + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + cursor: str | Unset = UNSET, + from_: datetime.datetime | Unset = UNSET, + to: datetime.datetime | Unset = UNSET, + account_id: UUID | Unset = UNSET, +) -> Response[CustomerPage | ErrorResponse]: + """List the seller's customers + + The other parties of the seller's sales and purchases, as written on their invoices: a customer is + created with the invoice of its first order. The most recently created first (by `createdAt`, then + `id`), one page at a time: pass the `nextCursor` of a page as `cursor` to read the next one, with + the same filters (a cursor reused with other filters is an `invalid_cursor`). `from` and `to` go + straight to a period of creation: `from` is inclusive, `to` exclusive. Requires `customers:read`. + + Args: + limit (int | Unset): Default: 50. + cursor (str | Unset): + from_ (datetime.datetime | Unset): + to (datetime.datetime | Unset): + account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[CustomerPage | ErrorResponse] + """ + + kwargs = _get_kwargs( + limit=limit, + cursor=cursor, + from_=from_, + to=to, + account_id=account_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + *, + client: AuthenticatedClient | Client, + limit: int | Unset = 50, + cursor: str | Unset = UNSET, + from_: datetime.datetime | Unset = UNSET, + to: datetime.datetime | Unset = UNSET, + account_id: UUID | Unset = UNSET, +) -> CustomerPage | ErrorResponse | None: + """List the seller's customers + + The other parties of the seller's sales and purchases, as written on their invoices: a customer is + created with the invoice of its first order. The most recently created first (by `createdAt`, then + `id`), one page at a time: pass the `nextCursor` of a page as `cursor` to read the next one, with + the same filters (a cursor reused with other filters is an `invalid_cursor`). `from` and `to` go + straight to a period of creation: `from` is inclusive, `to` exclusive. Requires `customers:read`. + + Args: + limit (int | Unset): Default: 50. + cursor (str | Unset): + from_ (datetime.datetime | Unset): + to (datetime.datetime | Unset): + account_id (UUID | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + CustomerPage | ErrorResponse + """ + + return ( + await asyncio_detailed( + client=client, + limit=limit, + cursor=cursor, + from_=from_, + to=to, + account_id=account_id, + ) + ).parsed diff --git a/src/controlresell/generated/models/__init__.py b/src/controlresell/generated/models/__init__.py index c52e098..f9e5dcb 100644 --- a/src/controlresell/generated/models/__init__.py +++ b/src/controlresell/generated/models/__init__.py @@ -6,6 +6,9 @@ from .api_error_params import ApiErrorParams from .conversation import Conversation from .conversation_page import ConversationPage +from .customer import Customer +from .customer_address import CustomerAddress +from .customer_page import CustomerPage from .error_response import ErrorResponse from .item import Item from .item_create_request import ItemCreateRequest @@ -38,6 +41,9 @@ "ApiErrorParams", "Conversation", "ConversationPage", + "Customer", + "CustomerAddress", + "CustomerPage", "ErrorResponse", "Item", "ItemCreateRequest", diff --git a/src/controlresell/generated/models/customer.py b/src/controlresell/generated/models/customer.py new file mode 100644 index 0000000..55e01d1 --- /dev/null +++ b/src/controlresell/generated/models/customer.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar, cast +from uuid import UUID + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.customer_address import CustomerAddress + + +T = TypeVar("T", bound="Customer") + + +@_attrs_define +class Customer: + """ + Attributes: + id (UUID): + first_name (str): + last_name (str): + address (CustomerAddress): + created_at (datetime.datetime): + email (None | str | Unset): + """ + + id: UUID + first_name: str + last_name: str + address: CustomerAddress + created_at: datetime.datetime + email: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + id = str(self.id) + + first_name = self.first_name + + last_name = self.last_name + + address = self.address.to_dict() + + created_at = self.created_at.isoformat() + + email: None | str | Unset + if isinstance(self.email, Unset): + email = UNSET + else: + email = self.email + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "id": id, + "firstName": first_name, + "lastName": last_name, + "address": address, + "createdAt": created_at, + } + ) + if email is not UNSET: + field_dict["email"] = email + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.customer_address import CustomerAddress + + d = dict(src_dict) + id = UUID(d.pop("id")) + + first_name = d.pop("firstName") + + last_name = d.pop("lastName") + + address = CustomerAddress.from_dict(d.pop("address")) + + created_at = datetime.datetime.fromisoformat(d.pop("createdAt")) + + def _parse_email(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + email = _parse_email(d.pop("email", UNSET)) + + customer = cls( + id=id, + first_name=first_name, + last_name=last_name, + address=address, + created_at=created_at, + email=email, + ) + + customer.additional_properties = d + return customer + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/controlresell/generated/models/customer_address.py b/src/controlresell/generated/models/customer_address.py new file mode 100644 index 0000000..8966427 --- /dev/null +++ b/src/controlresell/generated/models/customer_address.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CustomerAddress") + + +@_attrs_define +class CustomerAddress: + """ + Attributes: + line1 (None | str | Unset): + line2 (None | str | Unset): + city (None | str | Unset): + postal_code (None | str | Unset): + country (None | str | Unset): + """ + + line1: None | str | Unset = UNSET + line2: None | str | Unset = UNSET + city: None | str | Unset = UNSET + postal_code: None | str | Unset = UNSET + country: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + line1: None | str | Unset + if isinstance(self.line1, Unset): + line1 = UNSET + else: + line1 = self.line1 + + line2: None | str | Unset + if isinstance(self.line2, Unset): + line2 = UNSET + else: + line2 = self.line2 + + city: None | str | Unset + if isinstance(self.city, Unset): + city = UNSET + else: + city = self.city + + postal_code: None | str | Unset + if isinstance(self.postal_code, Unset): + postal_code = UNSET + else: + postal_code = self.postal_code + + country: None | str | Unset + if isinstance(self.country, Unset): + country = UNSET + else: + country = self.country + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if line1 is not UNSET: + field_dict["line1"] = line1 + if line2 is not UNSET: + field_dict["line2"] = line2 + if city is not UNSET: + field_dict["city"] = city + if postal_code is not UNSET: + field_dict["postalCode"] = postal_code + if country is not UNSET: + field_dict["country"] = country + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + + def _parse_line1(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + line1 = _parse_line1(d.pop("line1", UNSET)) + + def _parse_line2(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + line2 = _parse_line2(d.pop("line2", UNSET)) + + def _parse_city(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + city = _parse_city(d.pop("city", UNSET)) + + def _parse_postal_code(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + postal_code = _parse_postal_code(d.pop("postalCode", UNSET)) + + def _parse_country(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + country = _parse_country(d.pop("country", UNSET)) + + customer_address = cls( + line1=line1, + line2=line2, + city=city, + postal_code=postal_code, + country=country, + ) + + customer_address.additional_properties = d + return customer_address + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/controlresell/generated/models/customer_page.py b/src/controlresell/generated/models/customer_page.py new file mode 100644 index 0000000..7bf3350 --- /dev/null +++ b/src/controlresell/generated/models/customer_page.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.customer import Customer + + +T = TypeVar("T", bound="CustomerPage") + + +@_attrs_define +class CustomerPage: + """ + Attributes: + data (list[Customer]): + next_cursor (None | str | Unset): + """ + + data: list[Customer] + next_cursor: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + data = [] + for data_item_data in self.data: + data_item = data_item_data.to_dict() + data.append(data_item) + + next_cursor: None | str | Unset + if isinstance(self.next_cursor, Unset): + next_cursor = UNSET + else: + next_cursor = self.next_cursor + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "data": data, + } + ) + if next_cursor is not UNSET: + field_dict["nextCursor"] = next_cursor + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.customer import Customer + + d = dict(src_dict) + data = [] + _data = d.pop("data") + for data_item_data in _data: + data_item = Customer.from_dict(data_item_data) + + data.append(data_item) + + def _parse_next_cursor(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + next_cursor = _parse_next_cursor(d.pop("nextCursor", UNSET)) + + customer_page = cls( + data=data, + next_cursor=next_cursor, + ) + + customer_page.additional_properties = d + return customer_page + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/tests/test_client.py b/tests/test_client.py index 5bc295e..95370e7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -18,6 +18,16 @@ FAST = RetryPolicy(base_delay=0.001, max_delay=0.005) +CUSTOMER = { + "id": "11111111-1111-1111-1111-111111111111", + "firstName": "Ann", + "lastName": "Doe", + "email": None, + "address": {"country": "FR"}, + "createdAt": "2026-10-21T10:00:00Z", +} + + def reply(body, status=200, headers=None): return httpx.Response(status, json=body, headers=headers or {}) @@ -331,6 +341,32 @@ def handler(request): assert seen[1].params["cursor"] == "c2" +def test_customers_iter_follows_the_cursor_with_the_same_filters(): + import datetime + + seen = [] + + def handler(request): + seen.append(request.url) + if request.url.params.get("cursor"): + return reply({"data": [{**CUSTOMER, "firstName": "Bob"}], "nextCursor": None}) + return reply({"data": [CUSTOMER], "nextCursor": "c2"}) + + _, sdk = client(handler) + start = datetime.datetime(2026, 10, 1, tzinfo=datetime.timezone.utc) + names = [c.first_name for c in sdk.customers.iter(account_id=ACCOUNT, from_=start, limit=1)] + assert names == ["Ann", "Bob"] + assert [u.path for u in seen] == ["/api/public/v1/customers"] * 2 + assert [u.params["accountId"] for u in seen] == [ACCOUNT, ACCOUNT] + assert seen[0].params["from"] == start.isoformat() and seen[1].params["cursor"] == "c2" + + +def test_customers_get_reads_one_customer(): + requests, sdk = client(lambda r: reply(CUSTOMER)) + assert sdk.customers.get(ACCOUNT).first_name == "Ann" + assert requests[0].url.path == f"/api/public/v1/customers/{ACCOUNT}" + + def test_conversations_messages_and_list_send_the_from_and_to_window(): import datetime