From bca5b1c9b5f65743883ca48b54de22bffd7c6560 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:33:09 +0100 Subject: [PATCH 01/26] chore(internal): minor core client restructuring (#374) --- src/lithic/_base_client.py | 5 ++++- src/lithic/_streaming.py | 34 ++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/lithic/_base_client.py b/src/lithic/_base_client.py index 73bd2411..dda280f6 100644 --- a/src/lithic/_base_client.py +++ b/src/lithic/_base_client.py @@ -79,7 +79,7 @@ RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER, ) -from ._streaming import Stream, AsyncStream +from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder from ._exceptions import ( APIStatusError, APITimeoutError, @@ -431,6 +431,9 @@ def _prepare_url(self, url: str) -> URL: return merge_url + def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder: + return SSEDecoder() + def _build_request( self, options: FinalRequestOptions, diff --git a/src/lithic/_streaming.py b/src/lithic/_streaming.py index 2689d4d3..591cfa88 100644 --- a/src/lithic/_streaming.py +++ b/src/lithic/_streaming.py @@ -5,7 +5,7 @@ import inspect from types import TracebackType from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast -from typing_extensions import Self, TypeGuard, override, get_origin +from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -23,6 +23,8 @@ class Stream(Generic[_T]): response: httpx.Response + _decoder: SSEDecoder | SSEBytesDecoder + def __init__( self, *, @@ -33,7 +35,7 @@ def __init__( self.response = response self._cast_to = cast_to self._client = client - self._decoder = SSEDecoder() + self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() def __next__(self) -> _T: @@ -44,7 +46,10 @@ def __iter__(self) -> Iterator[_T]: yield item def _iter_events(self) -> Iterator[ServerSentEvent]: - yield from self._decoder.iter(self.response.iter_lines()) + if isinstance(self._decoder, SSEBytesDecoder): + yield from self._decoder.iter_bytes(self.response.iter_bytes()) + else: + yield from self._decoder.iter(self.response.iter_lines()) def __stream__(self) -> Iterator[_T]: cast_to = cast(Any, self._cast_to) @@ -84,6 +89,8 @@ class AsyncStream(Generic[_T]): response: httpx.Response + _decoder: SSEDecoder | SSEBytesDecoder + def __init__( self, *, @@ -94,7 +101,7 @@ def __init__( self.response = response self._cast_to = cast_to self._client = client - self._decoder = SSEDecoder() + self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() async def __anext__(self) -> _T: @@ -105,8 +112,12 @@ async def __aiter__(self) -> AsyncIterator[_T]: yield item async def _iter_events(self) -> AsyncIterator[ServerSentEvent]: - async for sse in self._decoder.aiter(self.response.aiter_lines()): - yield sse + if isinstance(self._decoder, SSEBytesDecoder): + async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()): + yield sse + else: + async for sse in self._decoder.aiter(self.response.aiter_lines()): + yield sse async def __stream__(self) -> AsyncIterator[_T]: cast_to = cast(Any, self._cast_to) @@ -259,6 +270,17 @@ def decode(self, line: str) -> ServerSentEvent | None: return None +@runtime_checkable +class SSEBytesDecoder(Protocol): + def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]: + """Given an iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]: + """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered""" + ... + + def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" origin = get_origin(typ) or typ From ea836172c1533eea4008732215b72d3f8ef0065d Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:40:41 +0100 Subject: [PATCH 02/26] docs(contributing): improve wording (#376) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45814ef0..afd23e01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,7 +82,7 @@ pip install ./path-to-wheel-file.whl ## Running tests -Most tests will require you to [setup a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. +Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. ```bash # you will need npm installed @@ -117,7 +117,7 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/lithic-com/lithic-python/actions/workflows/publish-pypi.yml). This will require a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/lithic-com/lithic-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually From 8b6e48c5f72df0f8380dbde642ccb31c809c9e30 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:57:12 +0100 Subject: [PATCH 03/26] chore(docs): mention install from git repo (#377) --- CONTRIBUTING.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index afd23e01..deb22147 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```bash -pip install git+ssh://git@github.com:lithic-com/lithic-python.git +pip install git+ssh://git@github.com/lithic-com/lithic-python.git ``` Alternatively, you can build from source and install the wheel file: diff --git a/README.md b/README.md index 1b6bf7a5..41b0e96a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ The REST API documentation can be found [on docs.lithic.com](https://docs.lithic ## Installation ```sh +# install from PyPI pip install lithic ``` From 0cecc6324700a8d1b38222ff032b5f492a0e663b Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:52:48 +0100 Subject: [PATCH 04/26] chore(internal): split up transforms into sync / async (#378) --- src/lithic/_utils/__init__.py | 2 + src/lithic/_utils/_transform.py | 128 +++++++++- src/lithic/resources/account_holders.py | 14 +- src/lithic/resources/accounts/accounts.py | 7 +- .../accounts/credit_configurations.py | 7 +- src/lithic/resources/auth_rules.py | 13 +- src/lithic/resources/cards/cards.py | 20 +- src/lithic/resources/disputes.py | 11 +- src/lithic/resources/events/subscriptions.py | 15 +- .../external_bank_accounts.py | 10 +- .../external_bank_accounts/micro_deposits.py | 7 +- .../financial_accounts/financial_accounts.py | 11 +- src/lithic/resources/payments.py | 11 +- src/lithic/resources/responder_endpoints.py | 13 +- .../resources/three_ds/authentication.py | 7 +- src/lithic/resources/tokenizations.py | 7 +- src/lithic/resources/transactions.py | 19 +- tests/test_transform.py | 220 ++++++++++++------ 18 files changed, 387 insertions(+), 135 deletions(-) diff --git a/src/lithic/_utils/__init__.py b/src/lithic/_utils/__init__.py index b5790a87..56978941 100644 --- a/src/lithic/_utils/__init__.py +++ b/src/lithic/_utils/__init__.py @@ -44,5 +44,7 @@ from ._transform import ( PropertyInfo as PropertyInfo, transform as transform, + async_transform as async_transform, maybe_transform as maybe_transform, + async_maybe_transform as async_maybe_transform, ) diff --git a/src/lithic/_utils/_transform.py b/src/lithic/_utils/_transform.py index 2cb7726c..9c769306 100644 --- a/src/lithic/_utils/_transform.py +++ b/src/lithic/_utils/_transform.py @@ -180,11 +180,7 @@ def _transform_recursive( if isinstance(data, pydantic.BaseModel): return model_dump(data, exclude_unset=True) - return _transform_value(data, annotation) - - -def _transform_value(data: object, type_: type) -> object: - annotated_type = _get_annotated_type(type_) + annotated_type = _get_annotated_type(annotation) if annotated_type is None: return data @@ -222,3 +218,125 @@ def _transform_typeddict( else: result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_) return result + + +async def async_maybe_transform( + data: object, + expected_type: object, +) -> Any | None: + """Wrapper over `async_transform()` that allows `None` to be passed. + + See `async_transform()` for more details. + """ + if data is None: + return None + return await async_transform(data, expected_type) + + +async def async_transform( + data: _T, + expected_type: object, +) -> _T: + """Transform dictionaries based off of type information from the given type, for example: + + ```py + class Params(TypedDict, total=False): + card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]] + + + transformed = transform({"card_id": ""}, Params) + # {'cardID': ''} + ``` + + Any keys / data that does not have type information given will be included as is. + + It should be noted that the transformations that this function does are not represented in the type system. + """ + transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type)) + return cast(_T, transformed) + + +async def _async_transform_recursive( + data: object, + *, + annotation: type, + inner_type: type | None = None, +) -> object: + """Transform the given data against the expected type. + + Args: + annotation: The direct type annotation given to the particular piece of data. + This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc + + inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type + is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in + the list can be transformed using the metadata from the container type. + + Defaults to the same value as the `annotation` argument. + """ + if inner_type is None: + inner_type = annotation + + stripped_type = strip_annotated_type(inner_type) + if is_typeddict(stripped_type) and is_mapping(data): + return await _async_transform_typeddict(data, stripped_type) + + if ( + # List[T] + (is_list_type(stripped_type) and is_list(data)) + # Iterable[T] + or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str)) + ): + inner_type = extract_type_arg(stripped_type, 0) + return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data] + + if is_union_type(stripped_type): + # For union types we run the transformation against all subtypes to ensure that everything is transformed. + # + # TODO: there may be edge cases where the same normalized field name will transform to two different names + # in different subtypes. + for subtype in get_args(stripped_type): + data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype) + return data + + if isinstance(data, pydantic.BaseModel): + return model_dump(data, exclude_unset=True) + + annotated_type = _get_annotated_type(annotation) + if annotated_type is None: + return data + + # ignore the first argument as it is the actual type + annotations = get_args(annotated_type)[1:] + for annotation in annotations: + if isinstance(annotation, PropertyInfo) and annotation.format is not None: + return await _async_format_data(data, annotation.format, annotation.format_template) + + return data + + +async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object: + if isinstance(data, (date, datetime)): + if format_ == "iso8601": + return data.isoformat() + + if format_ == "custom" and format_template is not None: + return data.strftime(format_template) + + return data + + +async def _async_transform_typeddict( + data: Mapping[str, object], + expected_type: type, +) -> Mapping[str, object]: + result: dict[str, object] = {} + annotations = get_type_hints(expected_type, include_extras=True) + for key, value in data.items(): + type_ = annotations.get(key) + if type_ is None: + # we do not have a type annotation for this field, leave it as is + result[key] = value + else: + result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) + return result diff --git a/src/lithic/resources/account_holders.py b/src/lithic/resources/account_holders.py index 39981086..d2ba086b 100644 --- a/src/lithic/resources/account_holders.py +++ b/src/lithic/resources/account_holders.py @@ -22,7 +22,11 @@ account_holder_upload_document_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import required_args, maybe_transform +from .._utils import ( + required_args, + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -958,7 +962,7 @@ async def create( ) -> AccountHolderCreateResponse: return await self._post( "/account_holders", - body=maybe_transform( + body=await async_maybe_transform( { "beneficial_owner_entities": beneficial_owner_entities, "beneficial_owner_individuals": beneficial_owner_individuals, @@ -1068,7 +1072,7 @@ async def update( ) return await self._patch( f"/account_holders/{account_holder_token}", - body=maybe_transform( + body=await async_maybe_transform( { "business_account_token": business_account_token, "email": email, @@ -1236,7 +1240,7 @@ async def resubmit( ) return await self._post( f"/account_holders/{account_holder_token}/resubmit", - body=maybe_transform( + body=await async_maybe_transform( { "individual": individual, "tos_timestamp": tos_timestamp, @@ -1350,7 +1354,7 @@ async def upload_document( ) return await self._post( f"/account_holders/{account_holder_token}/documents", - body=maybe_transform( + body=await async_maybe_transform( {"document_type": document_type}, account_holder_upload_document_params.AccountHolderUploadDocumentParams, ), diff --git a/src/lithic/resources/accounts/accounts.py b/src/lithic/resources/accounts/accounts.py index 74547a0d..be098d53 100644 --- a/src/lithic/resources/accounts/accounts.py +++ b/src/lithic/resources/accounts/accounts.py @@ -11,7 +11,10 @@ from ... import _legacy_response from ...types import Account, AccountSpendLimits, account_list_params, account_update_params from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -354,7 +357,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return await self._patch( f"/accounts/{account_token}", - body=maybe_transform( + body=await async_maybe_transform( { "daily_spend_limit": daily_spend_limit, "lifetime_spend_limit": lifetime_spend_limit, diff --git a/src/lithic/resources/accounts/credit_configurations.py b/src/lithic/resources/accounts/credit_configurations.py index 39e55d2a..b339f127 100644 --- a/src/lithic/resources/accounts/credit_configurations.py +++ b/src/lithic/resources/accounts/credit_configurations.py @@ -7,7 +7,10 @@ from ... import _legacy_response from ...types import BusinessAccount from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -197,7 +200,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `account_token` but received {account_token!r}") return await self._patch( f"/accounts/{account_token}/credit_configuration", - body=maybe_transform( + body=await async_maybe_transform( { "billing_period": billing_period, "credit_limit": credit_limit, diff --git a/src/lithic/resources/auth_rules.py b/src/lithic/resources/auth_rules.py index 159f0a03..293129b4 100644 --- a/src/lithic/resources/auth_rules.py +++ b/src/lithic/resources/auth_rules.py @@ -18,7 +18,10 @@ auth_rule_update_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from .._utils import ( + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -426,7 +429,7 @@ async def create( """ return await self._post( "/auth_rules", - body=maybe_transform( + body=await async_maybe_transform( { "account_tokens": account_tokens, "allowed_countries": allowed_countries, @@ -525,7 +528,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") return await self._put( f"/auth_rules/{auth_rule_token}", - body=maybe_transform( + body=await async_maybe_transform( { "allowed_countries": allowed_countries, "allowed_mcc": allowed_mcc, @@ -633,7 +636,7 @@ async def apply( raise ValueError(f"Expected a non-empty value for `auth_rule_token` but received {auth_rule_token!r}") return await self._post( f"/auth_rules/{auth_rule_token}/apply", - body=maybe_transform( + body=await async_maybe_transform( { "account_tokens": account_tokens, "card_tokens": card_tokens, @@ -684,7 +687,7 @@ async def remove( """ return await self._delete( "/auth_rules/remove", - body=maybe_transform( + body=await async_maybe_transform( { "account_tokens": account_tokens, "card_tokens": card_tokens, diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index d2c1234b..b997a007 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -31,7 +31,11 @@ card_search_by_pan_params, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform, strip_not_given +from ..._utils import ( + maybe_transform, + strip_not_given, + async_maybe_transform, +) from .balances import ( Balances, AsyncBalances, @@ -1089,7 +1093,7 @@ async def create( """ return await self._post( "/cards", - body=maybe_transform( + body=await async_maybe_transform( { "type": type, "account_token": account_token, @@ -1233,7 +1237,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._patch( f"/cards/{card_token}", - body=maybe_transform( + body=await async_maybe_transform( { "auth_rule_token": auth_rule_token, "digital_card_art_token": digital_card_art_token, @@ -1383,7 +1387,7 @@ async def embed( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( { "embed_request": embed_request, "hmac": hmac, @@ -1556,7 +1560,7 @@ async def provision( raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._post( f"/cards/{card_token}/provision", - body=maybe_transform( + body=await async_maybe_transform( { "certificate": certificate, "digital_wallet": digital_wallet, @@ -1626,7 +1630,7 @@ async def reissue( raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._post( f"/cards/{card_token}/reissue", - body=maybe_transform( + body=await async_maybe_transform( { "carrier": carrier, "product_id": product_id, @@ -1704,7 +1708,7 @@ async def renew( raise ValueError(f"Expected a non-empty value for `card_token` but received {card_token!r}") return await self._post( f"/cards/{card_token}/renew", - body=maybe_transform( + body=await async_maybe_transform( { "shipping_address": shipping_address, "carrier": carrier, @@ -1789,7 +1793,7 @@ async def search_by_pan( """ return await self._post( "/cards/search_by_pan", - body=maybe_transform({"pan": pan}, card_search_by_pan_params.CardSearchByPanParams), + body=await async_maybe_transform({"pan": pan}, card_search_by_pan_params.CardSearchByPanParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/lithic/resources/disputes.py b/src/lithic/resources/disputes.py index 627d2bea..ef613e18 100644 --- a/src/lithic/resources/disputes.py +++ b/src/lithic/resources/disputes.py @@ -28,7 +28,10 @@ NotGiven, FileTypes, ) -from .._utils import maybe_transform +from .._utils import ( + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -609,7 +612,7 @@ async def create( """ return await self._post( "/disputes", - body=maybe_transform( + body=await async_maybe_transform( { "amount": amount, "reason": reason, @@ -714,7 +717,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return await self._patch( f"/disputes/{dispute_token}", - body=maybe_transform( + body=await async_maybe_transform( { "amount": amount, "customer_filed_date": customer_filed_date, @@ -917,7 +920,7 @@ async def initiate_evidence_upload( raise ValueError(f"Expected a non-empty value for `dispute_token` but received {dispute_token!r}") return await self._post( f"/disputes/{dispute_token}/evidences", - body=maybe_transform( + body=await async_maybe_transform( {"filename": filename}, dispute_initiate_evidence_upload_params.DisputeInitiateEvidenceUploadParams ), options=make_request_options( diff --git a/src/lithic/resources/events/subscriptions.py b/src/lithic/resources/events/subscriptions.py index 331a9779..b5e7d2d9 100644 --- a/src/lithic/resources/events/subscriptions.py +++ b/src/lithic/resources/events/subscriptions.py @@ -11,7 +11,10 @@ from ... import _legacy_response from ...types import MessageAttempt, EventSubscription from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from ..._utils import maybe_transform +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -699,7 +702,7 @@ async def create( """ return await self._post( "/event_subscriptions", - body=maybe_transform( + body=await async_maybe_transform( { "url": url, "description": description, @@ -812,7 +815,7 @@ async def update( ) return await self._patch( f"/event_subscriptions/{event_subscription_token}", - body=maybe_transform( + body=await async_maybe_transform( { "url": url, "description": description, @@ -1027,7 +1030,7 @@ async def recover( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( { "begin": begin, "end": end, @@ -1085,7 +1088,7 @@ async def replay_missing( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( { "begin": begin, "end": end, @@ -1219,7 +1222,7 @@ async def send_simulated_example( ) return await self._post( f"/simulate/event_subscriptions/{event_subscription_token}/send_example", - body=maybe_transform( + body=await async_maybe_transform( {"event_type": event_type}, subscription_send_simulated_example_params.SubscriptionSendSimulatedExampleParams, ), diff --git a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py index aa2580a3..41e97093 100644 --- a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py +++ b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py @@ -23,7 +23,11 @@ external_bank_account_update_params, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import required_args, maybe_transform +from ..._utils import ( + required_args, + maybe_transform, + async_maybe_transform, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -554,7 +558,7 @@ async def create( ) -> ExternalBankAccountCreateResponse: return await self._post( "/external_bank_accounts", - body=maybe_transform( + body=await async_maybe_transform( { "account_number": account_number, "country": country, @@ -659,7 +663,7 @@ async def update( ) return await self._patch( f"/external_bank_accounts/{external_bank_account_token}", - body=maybe_transform( + body=await async_maybe_transform( { "address": address, "company_id": company_id, diff --git a/src/lithic/resources/external_bank_accounts/micro_deposits.py b/src/lithic/resources/external_bank_accounts/micro_deposits.py index 82066717..17adb25b 100644 --- a/src/lithic/resources/external_bank_accounts/micro_deposits.py +++ b/src/lithic/resources/external_bank_accounts/micro_deposits.py @@ -8,7 +8,10 @@ from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -108,7 +111,7 @@ async def create( ) return await self._post( f"/external_bank_accounts/{external_bank_account_token}/micro_deposits", - body=maybe_transform( + body=await async_maybe_transform( {"micro_deposits": micro_deposits}, micro_deposit_create_params.MicroDepositCreateParams ), options=make_request_options( diff --git a/src/lithic/resources/financial_accounts/financial_accounts.py b/src/lithic/resources/financial_accounts/financial_accounts.py index 6d29a6f9..4239d097 100644 --- a/src/lithic/resources/financial_accounts/financial_accounts.py +++ b/src/lithic/resources/financial_accounts/financial_accounts.py @@ -14,7 +14,10 @@ financial_account_update_params, ) from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) from .balances import ( Balances, AsyncBalances, @@ -287,7 +290,7 @@ async def create( """ return await self._post( "/financial_accounts", - body=maybe_transform( + body=await async_maybe_transform( { "nickname": nickname, "type": type, @@ -366,7 +369,9 @@ async def update( ) return await self._patch( f"/financial_accounts/{financial_account_token}", - body=maybe_transform({"nickname": nickname}, financial_account_update_params.FinancialAccountUpdateParams), + body=await async_maybe_transform( + {"nickname": nickname}, financial_account_update_params.FinancialAccountUpdateParams + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/lithic/resources/payments.py b/src/lithic/resources/payments.py index b0aea627..49feee65 100644 --- a/src/lithic/resources/payments.py +++ b/src/lithic/resources/payments.py @@ -21,7 +21,10 @@ payment_simulate_release_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from .._utils import ( + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -353,7 +356,7 @@ async def create( """ return await self._post( "/payments", - body=maybe_transform( + body=await async_maybe_transform( { "amount": amount, "external_bank_account_token": external_bank_account_token, @@ -533,7 +536,7 @@ async def simulate_release( """ return await self._post( "/simulate/payments/release", - body=maybe_transform( + body=await async_maybe_transform( {"payment_token": payment_token}, payment_simulate_release_params.PaymentSimulateReleaseParams ), options=make_request_options( @@ -568,7 +571,7 @@ async def simulate_return( """ return await self._post( "/simulate/payments/return", - body=maybe_transform( + body=await async_maybe_transform( { "payment_token": payment_token, "return_reason_code": return_reason_code, diff --git a/src/lithic/resources/responder_endpoints.py b/src/lithic/resources/responder_endpoints.py index 4e9d4d8b..6a7d74a0 100644 --- a/src/lithic/resources/responder_endpoints.py +++ b/src/lithic/resources/responder_endpoints.py @@ -15,7 +15,10 @@ responder_endpoint_check_status_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from .._utils import maybe_transform +from .._utils import ( + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -194,7 +197,7 @@ async def create( """ return await self._post( "/responder_endpoints", - body=maybe_transform( + body=await async_maybe_transform( { "type": type, "url": url, @@ -239,7 +242,9 @@ async def delete( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"type": type}, responder_endpoint_delete_params.ResponderEndpointDeleteParams), + query=await async_maybe_transform( + {"type": type}, responder_endpoint_delete_params.ResponderEndpointDeleteParams + ), ), cast_to=NoneType, ) @@ -276,7 +281,7 @@ async def check_status( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( {"type": type}, responder_endpoint_check_status_params.ResponderEndpointCheckStatusParams ), ), diff --git a/src/lithic/resources/three_ds/authentication.py b/src/lithic/resources/three_ds/authentication.py index a84141ce..2f4d7f30 100644 --- a/src/lithic/resources/three_ds/authentication.py +++ b/src/lithic/resources/three_ds/authentication.py @@ -6,7 +6,10 @@ from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -189,7 +192,7 @@ async def simulate( """ return await self._post( "/three_ds_authentication/simulate", - body=maybe_transform( + body=await async_maybe_transform( { "merchant": merchant, "pan": pan, diff --git a/src/lithic/resources/tokenizations.py b/src/lithic/resources/tokenizations.py index 2aec5963..1dd4c6ee 100644 --- a/src/lithic/resources/tokenizations.py +++ b/src/lithic/resources/tokenizations.py @@ -17,7 +17,10 @@ tokenization_simulate_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from .._utils import ( + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -369,7 +372,7 @@ async def simulate( """ return await self._post( "/simulate/tokenizations", - body=maybe_transform( + body=await async_maybe_transform( { "cvv": cvv, "expiration_date": expiration_date, diff --git a/src/lithic/resources/transactions.py b/src/lithic/resources/transactions.py index 2cb36675..b26dcb63 100644 --- a/src/lithic/resources/transactions.py +++ b/src/lithic/resources/transactions.py @@ -28,7 +28,10 @@ transaction_simulate_credit_authorization_params, ) from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform +from .._utils import ( + maybe_transform, + async_maybe_transform, +) from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper @@ -780,7 +783,7 @@ async def simulate_authorization( """ return await self._post( "/simulate/authorize", - body=maybe_transform( + body=await async_maybe_transform( { "amount": amount, "descriptor": descriptor, @@ -833,7 +836,7 @@ async def simulate_authorization_advice( """ return await self._post( "/simulate/authorization_advice", - body=maybe_transform( + body=await async_maybe_transform( { "token": token, "amount": amount, @@ -887,7 +890,7 @@ async def simulate_clearing( """ return await self._post( "/simulate/clearing", - body=maybe_transform( + body=await async_maybe_transform( { "token": token, "amount": amount, @@ -946,7 +949,7 @@ async def simulate_credit_authorization( """ return await self._post( "/simulate/credit_authorization_advice", - body=maybe_transform( + body=await async_maybe_transform( { "amount": amount, "descriptor": descriptor, @@ -997,7 +1000,7 @@ async def simulate_return( """ return await self._post( "/simulate/return", - body=maybe_transform( + body=await async_maybe_transform( { "amount": amount, "descriptor": descriptor, @@ -1040,7 +1043,7 @@ async def simulate_return_reversal( """ return await self._post( "/simulate/return_reversal", - body=maybe_transform( + body=await async_maybe_transform( {"token": token}, transaction_simulate_return_reversal_params.TransactionSimulateReturnReversalParams ), options=make_request_options( @@ -1092,7 +1095,7 @@ async def simulate_void( """ return await self._post( "/simulate/void", - body=maybe_transform( + body=await async_maybe_transform( { "token": token, "amount": amount, diff --git a/tests/test_transform.py b/tests/test_transform.py index c57a6172..71f359b7 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1,22 +1,45 @@ from __future__ import annotations -from typing import Any, List, Union, Iterable, Optional, cast +from typing import Any, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict import pytest -from lithic._utils import PropertyInfo, transform, parse_datetime +from lithic._utils import ( + PropertyInfo, + transform as _transform, + parse_datetime, + async_transform as _async_transform, +) from lithic._compat import PYDANTIC_V2 from lithic._models import BaseModel +_T = TypeVar("_T") + + +async def transform( + data: _T, + expected_type: object, + use_async: bool, +) -> _T: + if use_async: + return await _async_transform(data, expected_type=expected_type) + + return _transform(data, expected_type=expected_type) + + +parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"]) + class Foo1(TypedDict): foo_bar: Annotated[str, PropertyInfo(alias="fooBar")] -def test_top_level_alias() -> None: - assert transform({"foo_bar": "hello"}, expected_type=Foo1) == {"fooBar": "hello"} +@parametrize +@pytest.mark.asyncio +async def test_top_level_alias(use_async: bool) -> None: + assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"} class Foo2(TypedDict): @@ -32,9 +55,11 @@ class Baz2(TypedDict): my_baz: Annotated[str, PropertyInfo(alias="myBaz")] -def test_recursive_typeddict() -> None: - assert transform({"bar": {"this_thing": 1}}, Foo2) == {"bar": {"this__thing": 1}} - assert transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2) == {"bar": {"Baz": {"myBaz": "foo"}}} +@parametrize +@pytest.mark.asyncio +async def test_recursive_typeddict(use_async: bool) -> None: + assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}} + assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}} class Foo3(TypedDict): @@ -45,8 +70,10 @@ class Bar3(TypedDict): my_field: Annotated[str, PropertyInfo(alias="myField")] -def test_list_of_typeddict() -> None: - result = transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, expected_type=Foo3) +@parametrize +@pytest.mark.asyncio +async def test_list_of_typeddict(use_async: bool) -> None: + result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async) assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]} @@ -62,10 +89,14 @@ class Baz4(TypedDict): foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] -def test_union_of_typeddict() -> None: - assert transform({"foo": {"foo_bar": "bar"}}, Foo4) == {"foo": {"fooBar": "bar"}} - assert transform({"foo": {"foo_baz": "baz"}}, Foo4) == {"foo": {"fooBaz": "baz"}} - assert transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4) == {"foo": {"fooBaz": "baz", "fooBar": "bar"}} +@parametrize +@pytest.mark.asyncio +async def test_union_of_typeddict(use_async: bool) -> None: + assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}} + assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}} + assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == { + "foo": {"fooBaz": "baz", "fooBar": "bar"} + } class Foo5(TypedDict): @@ -80,9 +111,11 @@ class Baz5(TypedDict): foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] -def test_union_of_list() -> None: - assert transform({"foo": {"foo_bar": "bar"}}, Foo5) == {"FOO": {"fooBar": "bar"}} - assert transform( +@parametrize +@pytest.mark.asyncio +async def test_union_of_list(use_async: bool) -> None: + assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}} + assert await transform( { "foo": [ {"foo_baz": "baz"}, @@ -90,6 +123,7 @@ def test_union_of_list() -> None: ] }, Foo5, + use_async, ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]} @@ -97,8 +131,10 @@ class Foo6(TypedDict): bar: Annotated[str, PropertyInfo(alias="Bar")] -def test_includes_unknown_keys() -> None: - assert transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6) == { +@parametrize +@pytest.mark.asyncio +async def test_includes_unknown_keys(use_async: bool) -> None: + assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == { "Bar": "bar", "baz_": {"FOO": 1}, } @@ -113,9 +149,11 @@ class Bar7(TypedDict): foo: str -def test_ignores_invalid_input() -> None: - assert transform({"bar": ""}, Foo7) == {"bAr": ""} - assert transform({"foo": ""}, Foo7) == {"foo": ""} +@parametrize +@pytest.mark.asyncio +async def test_ignores_invalid_input(use_async: bool) -> None: + assert await transform({"bar": ""}, Foo7, use_async) == {"bAr": ""} + assert await transform({"foo": ""}, Foo7, use_async) == {"foo": ""} class DatetimeDict(TypedDict, total=False): @@ -134,52 +172,66 @@ class DateDict(TypedDict, total=False): foo: Annotated[date, PropertyInfo(format="iso8601")] -def test_iso8601_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"foo": dt}, DatetimeDict) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] dt = dt.replace(tzinfo=None) - assert transform({"foo": dt}, DatetimeDict) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] + assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap] - assert transform({"foo": None}, DateDict) == {"foo": None} # type: ignore[comparison-overlap] - assert transform({"foo": date.fromisoformat("2023-02-23")}, DateDict) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] + assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap] + assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap] -def test_optional_iso8601_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_optional_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"bar": dt}, DatetimeDict) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] - assert transform({"bar": None}, DatetimeDict) == {"bar": None} + assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None} -def test_required_iso8601_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_required_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"required": dt}, DatetimeDict) == {"required": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] + assert await transform({"required": dt}, DatetimeDict, use_async) == { + "required": "2023-02-23T14:16:36.337692+00:00" + } # type: ignore[comparison-overlap] - assert transform({"required": None}, DatetimeDict) == {"required": None} + assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None} -def test_union_datetime() -> None: +@parametrize +@pytest.mark.asyncio +async def test_union_datetime(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - assert transform({"union": dt}, DatetimeDict) == { # type: ignore[comparison-overlap] + assert await transform({"union": dt}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] "union": "2023-02-23T14:16:36.337692+00:00" } - assert transform({"union": "foo"}, DatetimeDict) == {"union": "foo"} + assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"} -def test_nested_list_iso6801_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_nested_list_iso6801_format(use_async: bool) -> None: dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") dt2 = parse_datetime("2022-01-15T06:34:23Z") - assert transform({"list_": [dt1, dt2]}, DatetimeDict) == { # type: ignore[comparison-overlap] + assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap] "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"] } -def test_datetime_custom_format() -> None: +@parametrize +@pytest.mark.asyncio +async def test_datetime_custom_format(use_async: bool) -> None: dt = parse_datetime("2022-01-15T06:34:23Z") - result = transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")]) + result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async) assert result == "06" # type: ignore[comparison-overlap] @@ -187,47 +239,59 @@ class DateDictWithRequiredAlias(TypedDict, total=False): required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]] -def test_datetime_with_alias() -> None: - assert transform({"required_prop": None}, DateDictWithRequiredAlias) == {"prop": None} # type: ignore[comparison-overlap] - assert transform({"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias) == { - "prop": "2023-02-23" - } # type: ignore[comparison-overlap] +@parametrize +@pytest.mark.asyncio +async def test_datetime_with_alias(use_async: bool) -> None: + assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None} # type: ignore[comparison-overlap] + assert await transform( + {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async + ) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap] class MyModel(BaseModel): foo: str -def test_pydantic_model_to_dictionary() -> None: - assert transform(MyModel(foo="hi!"), Any) == {"foo": "hi!"} - assert transform(MyModel.construct(foo="hi!"), Any) == {"foo": "hi!"} +@parametrize +@pytest.mark.asyncio +async def test_pydantic_model_to_dictionary(use_async: bool) -> None: + assert await transform(MyModel(foo="hi!"), Any, use_async) == {"foo": "hi!"} + assert await transform(MyModel.construct(foo="hi!"), Any, use_async) == {"foo": "hi!"} -def test_pydantic_empty_model() -> None: - assert transform(MyModel.construct(), Any) == {} +@parametrize +@pytest.mark.asyncio +async def test_pydantic_empty_model(use_async: bool) -> None: + assert await transform(MyModel.construct(), Any, use_async) == {} -def test_pydantic_unknown_field() -> None: - assert transform(MyModel.construct(my_untyped_field=True), Any) == {"my_untyped_field": True} +@parametrize +@pytest.mark.asyncio +async def test_pydantic_unknown_field(use_async: bool) -> None: + assert await transform(MyModel.construct(my_untyped_field=True), Any, use_async) == {"my_untyped_field": True} -def test_pydantic_mismatched_types() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) if PYDANTIC_V2: with pytest.warns(UserWarning): - params = transform(model, Any) + params = await transform(model, Any, use_async) else: - params = transform(model, Any) + params = await transform(model, Any, use_async) assert params == {"foo": True} -def test_pydantic_mismatched_object_type() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_mismatched_object_type(use_async: bool) -> None: model = MyModel.construct(foo=MyModel.construct(hello="world")) if PYDANTIC_V2: with pytest.warns(UserWarning): - params = transform(model, Any) + params = await transform(model, Any, use_async) else: - params = transform(model, Any) + params = await transform(model, Any, use_async) assert params == {"foo": {"hello": "world"}} @@ -235,10 +299,12 @@ class ModelNestedObjects(BaseModel): nested: MyModel -def test_pydantic_nested_objects() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_nested_objects(use_async: bool) -> None: model = ModelNestedObjects.construct(nested={"foo": "stainless"}) assert isinstance(model.nested, MyModel) - assert transform(model, Any) == {"nested": {"foo": "stainless"}} + assert await transform(model, Any, use_async) == {"nested": {"foo": "stainless"}} class ModelWithDefaultField(BaseModel): @@ -247,24 +313,26 @@ class ModelWithDefaultField(BaseModel): with_str_default: str = "foo" -def test_pydantic_default_field() -> None: +@parametrize +@pytest.mark.asyncio +async def test_pydantic_default_field(use_async: bool) -> None: # should be excluded when defaults are used model = ModelWithDefaultField.construct() assert model.with_none_default is None assert model.with_str_default == "foo" - assert transform(model, Any) == {} + assert await transform(model, Any, use_async) == {} # should be included when the default value is explicitly given model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo") assert model.with_none_default is None assert model.with_str_default == "foo" - assert transform(model, Any) == {"with_none_default": None, "with_str_default": "foo"} + assert await transform(model, Any, use_async) == {"with_none_default": None, "with_str_default": "foo"} # should be included when a non-default value is explicitly given model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz") assert model.with_none_default == "bar" assert model.with_str_default == "baz" - assert transform(model, Any) == {"with_none_default": "bar", "with_str_default": "baz"} + assert await transform(model, Any, use_async) == {"with_none_default": "bar", "with_str_default": "baz"} class TypedDictIterableUnion(TypedDict): @@ -279,21 +347,33 @@ class Baz8(TypedDict): foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")] -def test_iterable_of_dictionaries() -> None: - assert transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion) == {"FOO": [{"fooBaz": "bar"}]} - assert cast(Any, transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion)) == {"FOO": [{"fooBaz": "bar"}]} +@parametrize +@pytest.mark.asyncio +async def test_iterable_of_dictionaries(use_async: bool) -> None: + assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == { + "FOO": [{"fooBaz": "bar"}] + } + assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == { + "FOO": [{"fooBaz": "bar"}] + } def my_iter() -> Iterable[Baz8]: yield {"foo_baz": "hello"} yield {"foo_baz": "world"} - assert transform({"foo": my_iter()}, TypedDictIterableUnion) == {"FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}]} + assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == { + "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}] + } class TypedDictIterableUnionStr(TypedDict): foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")] -def test_iterable_union_str() -> None: - assert transform({"foo": "bar"}, TypedDictIterableUnionStr) == {"FOO": "bar"} - assert cast(Any, transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]])) == [{"fooBaz": "bar"}] +@parametrize +@pytest.mark.asyncio +async def test_iterable_union_str(use_async: bool) -> None: + assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"} + assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [ + {"fooBaz": "bar"} + ] From 0cde41d7d8850d1d6bca08fdc1358241d62779de Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:40:26 +0100 Subject: [PATCH 05/26] chore(internal): support more input types (#379) --- src/lithic/_files.py | 5 +++ src/lithic/_types.py | 2 ++ src/lithic/_utils/_transform.py | 39 ++++++++++++++++++++++- src/lithic/resources/cards/cards.py | 21 ++++++++---- src/lithic/types/card_provision_params.py | 12 ++++--- tests/sample_file.txt | 1 + tests/test_transform.py | 29 +++++++++++++++++ 7 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 tests/sample_file.txt diff --git a/src/lithic/_files.py b/src/lithic/_files.py index b6e8af8b..0d2022ae 100644 --- a/src/lithic/_files.py +++ b/src/lithic/_files.py @@ -13,12 +13,17 @@ FileContent, RequestFiles, HttpxFileTypes, + Base64FileInput, HttpxFileContent, HttpxRequestFiles, ) from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: + return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) + + def is_file_content(obj: object) -> TypeGuard[FileContent]: return ( isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike) diff --git a/src/lithic/_types.py b/src/lithic/_types.py index e6b86b65..65ca0348 100644 --- a/src/lithic/_types.py +++ b/src/lithic/_types.py @@ -41,8 +41,10 @@ ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] ProxiesTypes = Union[str, Proxy, ProxiesDict] if TYPE_CHECKING: + Base64FileInput = Union[IO[bytes], PathLike[str]] FileContent = Union[IO[bytes], bytes, PathLike[str]] else: + Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. FileTypes = Union[ # file (or bytes) diff --git a/src/lithic/_utils/_transform.py b/src/lithic/_utils/_transform.py index 9c769306..1bd1330c 100644 --- a/src/lithic/_utils/_transform.py +++ b/src/lithic/_utils/_transform.py @@ -1,9 +1,13 @@ from __future__ import annotations +import io +import base64 +import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime from typing_extensions import Literal, get_args, override, get_type_hints +import anyio import pydantic from ._utils import ( @@ -11,6 +15,7 @@ is_mapping, is_iterable, ) +from .._files import is_base64_file_input from ._typing import ( is_list_type, is_union_type, @@ -29,7 +34,7 @@ # TODO: ensure works correctly with forward references in all cases -PropertyFormat = Literal["iso8601", "custom"] +PropertyFormat = Literal["iso8601", "base64", "custom"] class PropertyInfo: @@ -201,6 +206,22 @@ def _format_data(data: object, format_: PropertyFormat, format_template: str | N if format_ == "custom" and format_template is not None: return data.strftime(format_template) + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = data.read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + return data @@ -323,6 +344,22 @@ async def _async_format_data(data: object, format_: PropertyFormat, format_templ if format_ == "custom" and format_template is not None: return data.strftime(format_template) + if format_ == "base64" and is_base64_file_input(data): + binary: str | bytes | None = None + + if isinstance(data, pathlib.Path): + binary = await anyio.Path(data).read_bytes() + elif isinstance(data, io.IOBase): + binary = data.read() + + if isinstance(binary, str): # type: ignore[unreachable] + binary = binary.encode() + + if not isinstance(binary, bytes): + raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}") + + return base64.b64encode(binary).decode("ascii") + return data diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index b997a007..c56972fb 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -30,7 +30,14 @@ card_get_embed_url_params, card_search_by_pan_params, ) -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import ( + NOT_GIVEN, + Body, + Query, + Headers, + NotGiven, + Base64FileInput, +) from ..._utils import ( maybe_transform, strip_not_given, @@ -649,10 +656,10 @@ def provision( self, card_token: str, *, - certificate: str | NotGiven = NOT_GIVEN, + certificate: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | NotGiven = NOT_GIVEN, - nonce: str | NotGiven = NOT_GIVEN, - nonce_signature: str | NotGiven = NOT_GIVEN, + nonce: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, + nonce_signature: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, # 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, @@ -1513,10 +1520,10 @@ async def provision( self, card_token: str, *, - certificate: str | NotGiven = NOT_GIVEN, + certificate: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | NotGiven = NOT_GIVEN, - nonce: str | NotGiven = NOT_GIVEN, - nonce_signature: str | NotGiven = NOT_GIVEN, + nonce: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, + nonce_signature: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, # 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, diff --git a/src/lithic/types/card_provision_params.py b/src/lithic/types/card_provision_params.py index bde462a9..7d36f620 100644 --- a/src/lithic/types/card_provision_params.py +++ b/src/lithic/types/card_provision_params.py @@ -2,13 +2,17 @@ from __future__ import annotations -from typing_extensions import Literal, TypedDict +from typing import Union +from typing_extensions import Literal, Annotated, TypedDict + +from .._types import Base64FileInput +from .._utils import PropertyInfo __all__ = ["CardProvisionParams"] class CardProvisionParams(TypedDict, total=False): - certificate: str + certificate: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] """Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only `activationData` in the response. Apple's public leaf @@ -20,14 +24,14 @@ class CardProvisionParams(TypedDict, total=False): digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] """Name of digital wallet provider.""" - nonce: str + nonce: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] """Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only `activationData` in the response. Base64 cryptographic nonce provided by the device's wallet. """ - nonce_signature: str + nonce_signature: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] """Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only `activationData` in the response. Base64 cryptographic diff --git a/tests/sample_file.txt b/tests/sample_file.txt new file mode 100644 index 00000000..af5626b4 --- /dev/null +++ b/tests/sample_file.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/tests/test_transform.py b/tests/test_transform.py index 71f359b7..efadfe61 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1,11 +1,14 @@ from __future__ import annotations +import io +import pathlib from typing import Any, List, Union, TypeVar, Iterable, Optional, cast from datetime import date, datetime from typing_extensions import Required, Annotated, TypedDict import pytest +from lithic._types import Base64FileInput from lithic._utils import ( PropertyInfo, transform as _transform, @@ -17,6 +20,8 @@ _T = TypeVar("_T") +SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt") + async def transform( data: _T, @@ -377,3 +382,27 @@ async def test_iterable_union_str(use_async: bool) -> None: assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [ {"fooBaz": "bar"} ] + + +class TypedDictBase64Input(TypedDict): + foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")] + + +@parametrize +@pytest.mark.asyncio +async def test_base64_file_input(use_async: bool) -> None: + # strings are left as-is + assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"} + + # pathlib.Path is automatically converted to base64 + assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQo=" + } # type: ignore[comparison-overlap] + + # io instances are automatically converted to base64 + assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQ==" + } # type: ignore[comparison-overlap] + assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == { + "foo": "SGVsbG8sIHdvcmxkIQ==" + } # type: ignore[comparison-overlap] From 0ea38014e414fd60fd461992b52743ce8499ba66 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:35:41 +0100 Subject: [PATCH 06/26] chore(client): improve error message for invalid http_client argument (#380) --- src/lithic/_base_client.py | 10 ++++++++++ tests/test_client.py | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/lithic/_base_client.py b/src/lithic/_base_client.py index dda280f6..f431128e 100644 --- a/src/lithic/_base_client.py +++ b/src/lithic/_base_client.py @@ -780,6 +780,11 @@ def __init__( else: timeout = DEFAULT_TIMEOUT + if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}" + ) + super().__init__( version=version, limits=limits, @@ -1322,6 +1327,11 @@ def __init__( else: timeout = DEFAULT_TIMEOUT + if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError( + f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}" + ) + super().__init__( version=version, base_url=base_url, diff --git a/tests/test_client.py b/tests/test_client.py index 7c53d83b..e0918f0d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -291,6 +291,16 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + async def test_invalid_http_client(self) -> None: + with pytest.raises(TypeError, match="Invalid `http_client` arg"): + async with httpx.AsyncClient() as http_client: + Lithic( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) + def test_default_headers_option(self) -> None: client = Lithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} @@ -1059,6 +1069,16 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + def test_invalid_http_client(self) -> None: + with pytest.raises(TypeError, match="Invalid `http_client` arg"): + with httpx.Client() as http_client: + AsyncLithic( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=cast(Any, http_client), + ) + def test_default_headers_option(self) -> None: client = AsyncLithic( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} From cb5e9d27a6bacf861921e49b30cc1abee365530a Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Wed, 6 Mar 2024 22:03:10 +0100 Subject: [PATCH 07/26] chore(internal): add core support for deserializing into number response (#381) --- src/lithic/_legacy_response.py | 8 ++++++++ src/lithic/_response.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/lithic/_legacy_response.py b/src/lithic/_legacy_response.py index a13df7ab..4120d263 100644 --- a/src/lithic/_legacy_response.py +++ b/src/lithic/_legacy_response.py @@ -107,6 +107,8 @@ class MyModel(BaseModel): - `list` - `Union` - `str` + - `int` + - `float` - `httpx.Response` """ cache_key = to if to is not None else self._cast_to @@ -220,6 +222,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == str: return cast(R, response.text) + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + origin = get_origin(cast_to) or cast_to if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent): diff --git a/src/lithic/_response.py b/src/lithic/_response.py index 5e4db166..3db58861 100644 --- a/src/lithic/_response.py +++ b/src/lithic/_response.py @@ -172,6 +172,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == bytes: return cast(R, response.content) + if cast_to == int: + return cast(R, int(response.text)) + + if cast_to == float: + return cast(R, float(response.text)) + origin = get_origin(cast_to) or cast_to # handle the legacy binary response case @@ -277,6 +283,8 @@ class MyModel(BaseModel): - `list` - `Union` - `str` + - `int` + - `float` - `httpx.Response` """ cache_key = to if to is not None else self._cast_to From 8aad846ffad52816d6ee55ca64aaf3b92649998d Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Fri, 8 Mar 2024 11:11:53 +0100 Subject: [PATCH 08/26] chore(internal): bump pyright (#382) --- requirements-dev.lock | 2 +- src/lithic/_legacy_response.py | 4 ++-- src/lithic/_response.py | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 6078baa3..8213ba7a 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -63,7 +63,7 @@ pydantic==2.4.2 # via lithic pydantic-core==2.10.1 # via pydantic -pyright==1.1.351 +pyright==1.1.353 pytest==7.1.1 # via pytest-asyncio pytest-asyncio==0.21.1 diff --git a/src/lithic/_legacy_response.py b/src/lithic/_legacy_response.py index 4120d263..b039833b 100644 --- a/src/lithic/_legacy_response.py +++ b/src/lithic/_legacy_response.py @@ -315,7 +315,7 @@ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, LegacyAPIRespon @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "true" kwargs["extra_headers"] = extra_headers @@ -332,7 +332,7 @@ def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P @functools.wraps(func) async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "true" kwargs["extra_headers"] = extra_headers diff --git a/src/lithic/_response.py b/src/lithic/_response.py index 3db58861..8d413149 100644 --- a/src/lithic/_response.py +++ b/src/lithic/_response.py @@ -634,7 +634,7 @@ def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseCo @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" kwargs["extra_headers"] = extra_headers @@ -655,7 +655,7 @@ def async_to_streamed_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" kwargs["extra_headers"] = extra_headers @@ -679,7 +679,7 @@ def to_custom_streamed_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls @@ -704,7 +704,7 @@ def async_to_custom_streamed_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "stream" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls @@ -724,7 +724,7 @@ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]] @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers @@ -741,7 +741,7 @@ def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P @functools.wraps(func) async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers @@ -763,7 +763,7 @@ def to_custom_raw_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls @@ -786,7 +786,7 @@ def async_to_custom_raw_response_wrapper( @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: - extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})} extra_headers[RAW_RESPONSE_HEADER] = "raw" extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls From ded4ec2ef0ea794238b1658a989701b24d53d465 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:23:26 +0100 Subject: [PATCH 09/26] chore(internal): support parsing Annotated types (#383) --- src/lithic/_legacy_response.py | 11 +++++++++- src/lithic/_models.py | 14 ++++++++++++- src/lithic/_response.py | 11 +++++++++- tests/test_legacy_response.py | 19 +++++++++++++++++ tests/test_models.py | 16 +++++++++++++-- tests/test_response.py | 37 +++++++++++++++++++++++++++++++++- tests/utils.py | 6 ++++++ 7 files changed, 108 insertions(+), 6 deletions(-) diff --git a/src/lithic/_legacy_response.py b/src/lithic/_legacy_response.py index b039833b..e0177b2c 100644 --- a/src/lithic/_legacy_response.py +++ b/src/lithic/_legacy_response.py @@ -13,7 +13,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given +from ._utils import is_given, extract_type_arg, is_annotated_type from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -174,6 +174,10 @@ def elapsed(self) -> datetime.timedelta: return self.http_response.elapsed def _parse(self, *, to: type[_T] | None = None) -> R | _T: + # unwrap `Annotated[T, ...]` -> `T` + if to and is_annotated_type(to): + to = extract_type_arg(to, 0) + if self._stream: if to: if not is_stream_class_type(to): @@ -215,6 +219,11 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ) cast_to = to if to is not None else self._cast_to + + # unwrap `Annotated[T, ...]` -> `T` + if is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + if cast_to is NoneType: return cast(R, None) diff --git a/src/lithic/_models.py b/src/lithic/_models.py index 81089149..af68e618 100644 --- a/src/lithic/_models.py +++ b/src/lithic/_models.py @@ -30,7 +30,16 @@ AnyMapping, HttpxRequestFiles, ) -from ._utils import is_list, is_given, is_mapping, parse_date, parse_datetime, strip_not_given +from ._utils import ( + is_list, + is_given, + is_mapping, + parse_date, + parse_datetime, + strip_not_given, + extract_type_arg, + is_annotated_type, +) from ._compat import ( PYDANTIC_V2, ConfigDict, @@ -275,6 +284,9 @@ def construct_type(*, value: object, type_: type) -> object: If the given value does not match the expected type then it is returned as-is. """ + # unwrap `Annotated[T, ...]` -> `T` + if is_annotated_type(type_): + type_ = extract_type_arg(type_, 0) # we need to use the origin class for any types that are subscripted generics # e.g. Dict[str, object] diff --git a/src/lithic/_response.py b/src/lithic/_response.py index 8d413149..011143fd 100644 --- a/src/lithic/_response.py +++ b/src/lithic/_response.py @@ -25,7 +25,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_var_from_base +from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -121,6 +121,10 @@ def __repr__(self) -> str: ) def _parse(self, *, to: type[_T] | None = None) -> R | _T: + # unwrap `Annotated[T, ...]` -> `T` + if to and is_annotated_type(to): + to = extract_type_arg(to, 0) + if self._is_sse_stream: if to: if not is_stream_class_type(to): @@ -162,6 +166,11 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ) cast_to = to if to is not None else self._cast_to + + # unwrap `Annotated[T, ...]` -> `T` + if is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) + if cast_to is NoneType: return cast(R, None) diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py index 7bc981c5..3c0b0f2b 100644 --- a/tests/test_legacy_response.py +++ b/tests/test_legacy_response.py @@ -1,4 +1,6 @@ import json +from typing import cast +from typing_extensions import Annotated import httpx import pytest @@ -63,3 +65,20 @@ def test_response_parse_custom_model(client: Lithic) -> None: obj = response.parse(to=CustomModel) assert obj.foo == "hello!" assert obj.bar == 2 + + +def test_response_parse_annotated_type(client: Lithic) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 diff --git a/tests/test_models.py b/tests/test_models.py index 3c00abb2..08fe1463 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,14 +1,14 @@ import json from typing import Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal +from typing_extensions import Literal, Annotated import pytest import pydantic from pydantic import Field from lithic._compat import PYDANTIC_V2, parse_obj, model_dump, model_json -from lithic._models import BaseModel +from lithic._models import BaseModel, construct_type class BasicModel(BaseModel): @@ -571,3 +571,15 @@ class OurModel(BaseModel): foo: Optional[str] = None takes_pydantic(OurModel()) + + +def test_annotated_types() -> None: + class Model(BaseModel): + value: str + + m = construct_type( + value={"value": "foo"}, + type_=cast(Any, Annotated[Model, "random metadata"]), + ) + assert isinstance(m, Model) + assert m.value == "foo" diff --git a/tests/test_response.py b/tests/test_response.py index ae1587a7..b8f7e817 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,5 +1,6 @@ import json -from typing import List +from typing import List, cast +from typing_extensions import Annotated import httpx import pytest @@ -157,3 +158,37 @@ async def test_async_response_parse_custom_model(async_client: AsyncLithic) -> N obj = await response.parse(to=CustomModel) assert obj.foo == "hello!" assert obj.bar == 2 + + +def test_response_parse_annotated_type(client: Lithic) -> None: + response = APIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +async def test_async_response_parse_annotated_type(async_client: AsyncLithic) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse( + to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]), + ) + assert obj.foo == "hello!" + assert obj.bar == 2 diff --git a/tests/utils.py b/tests/utils.py index 4f4ed1ec..b4a51496 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -14,6 +14,8 @@ is_list, is_list_type, is_union_type, + extract_type_arg, + is_annotated_type, ) from lithic._compat import PYDANTIC_V2, field_outer_type, get_model_fields from lithic._models import BaseModel @@ -49,6 +51,10 @@ def assert_matches_type( path: list[str], allow_none: bool = False, ) -> None: + # unwrap `Annotated[T, ...]` -> `T` + if is_annotated_type(type_): + type_ = extract_type_arg(type_, 0) + if allow_none and value is None: return From 841497faed8a1b22adf6a724eb5b7e936276974e Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:48:56 +0100 Subject: [PATCH 10/26] chore: export NOT_GIVEN sentinel value (#384) --- src/lithic/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lithic/__init__.py b/src/lithic/__init__.py index 8b7691d9..f9fb6c18 100644 --- a/src/lithic/__init__.py +++ b/src/lithic/__init__.py @@ -1,7 +1,7 @@ # File generated from our OpenAPI spec by Stainless. from . import types -from ._types import NoneType, Transport, ProxiesTypes +from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path from ._client import ( ENVIRONMENTS, @@ -43,6 +43,8 @@ "NoneType", "Transport", "ProxiesTypes", + "NotGiven", + "NOT_GIVEN", "LithicError", "APIError", "APIStatusError", From 9062866fa7aa87f9f5c25c401638415ea4986a09 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:29:17 +0100 Subject: [PATCH 11/26] chore(internal): improve deserialisation of discriminated unions (#385) --- src/lithic/_models.py | 160 +++++++++++++++++++++++++++- src/lithic/_utils/_transform.py | 5 +- tests/test_models.py | 180 ++++++++++++++++++++++++++++++++ 3 files changed, 343 insertions(+), 2 deletions(-) diff --git a/src/lithic/_models.py b/src/lithic/_models.py index af68e618..88afa408 100644 --- a/src/lithic/_models.py +++ b/src/lithic/_models.py @@ -10,6 +10,7 @@ Protocol, Required, TypedDict, + TypeGuard, final, override, runtime_checkable, @@ -31,6 +32,7 @@ HttpxRequestFiles, ) from ._utils import ( + PropertyInfo, is_list, is_given, is_mapping, @@ -39,6 +41,7 @@ strip_not_given, extract_type_arg, is_annotated_type, + strip_annotated_type, ) from ._compat import ( PYDANTIC_V2, @@ -55,6 +58,9 @@ ) from ._constants import RAW_RESPONSE_HEADER +if TYPE_CHECKING: + from pydantic_core.core_schema import ModelField, ModelFieldsSchema + __all__ = ["BaseModel", "GenericModel"] _T = TypeVar("_T") @@ -268,7 +274,6 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: def is_basemodel(type_: type) -> bool: """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`""" - origin = get_origin(type_) or type_ if is_union(type_): for variant in get_args(type_): if is_basemodel(variant): @@ -276,6 +281,11 @@ def is_basemodel(type_: type) -> bool: return False + return is_basemodel_type(type_) + + +def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]: + origin = get_origin(type_) or type_ return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) @@ -286,7 +296,10 @@ def construct_type(*, value: object, type_: type) -> object: """ # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) + else: + meta = tuple() # we need to use the origin class for any types that are subscripted generics # e.g. Dict[str, object] @@ -299,6 +312,28 @@ def construct_type(*, value: object, type_: type) -> object: except Exception: pass + # if the type is a discriminated union then we want to construct the right variant + # in the union, even if the data doesn't match exactly, otherwise we'd break code + # that relies on the constructed class types, e.g. + # + # class FooType: + # kind: Literal['foo'] + # value: str + # + # class BarType: + # kind: Literal['bar'] + # value: int + # + # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then + # we'd end up constructing `FooType` when it should be `BarType`. + discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta) + if discriminator and is_mapping(value): + variant_value = value.get(discriminator.field_alias_from or discriminator.field_name) + if variant_value and isinstance(variant_value, str): + variant_type = discriminator.mapping.get(variant_value) + if variant_type: + return construct_type(type_=variant_type, value=value) + # if the data is not valid, use the first variant that doesn't fail while deserializing for variant in args: try: @@ -356,6 +391,129 @@ def construct_type(*, value: object, type_: type) -> object: return value +@runtime_checkable +class CachedDiscriminatorType(Protocol): + __discriminator__: DiscriminatorDetails + + +class DiscriminatorDetails: + field_name: str + """The name of the discriminator field in the variant class, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] + ``` + + Will result in field_name='type' + """ + + field_alias_from: str | None + """The name of the discriminator field in the API response, e.g. + + ```py + class Foo(BaseModel): + type: Literal['foo'] = Field(alias='type_from_api') + ``` + + Will result in field_alias_from='type_from_api' + """ + + mapping: dict[str, type] + """Mapping of discriminator value to variant type, e.g. + + {'foo': FooVariant, 'bar': BarVariant} + """ + + def __init__( + self, + *, + mapping: dict[str, type], + discriminator_field: str, + discriminator_alias: str | None, + ) -> None: + self.mapping = mapping + self.field_name = discriminator_field + self.field_alias_from = discriminator_alias + + +def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: + if isinstance(union, CachedDiscriminatorType): + return union.__discriminator__ + + discriminator_field_name: str | None = None + + for annotation in meta_annotations: + if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None: + discriminator_field_name = annotation.discriminator + break + + if not discriminator_field_name: + return None + + mapping: dict[str, type] = {} + discriminator_alias: str | None = None + + for variant in get_args(union): + variant = strip_annotated_type(variant) + if is_basemodel_type(variant): + if PYDANTIC_V2: + field = _extract_field_schema_pv2(variant, discriminator_field_name) + if not field: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field.get("serialization_alias") + + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in field_schema["expected"]: + if isinstance(entry, str): + mapping[entry] = variant + else: + field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + if not field_info: + continue + + # Note: if one variant defines an alias then they all should + discriminator_alias = field_info.alias + + if field_info.annotation and is_literal_type(field_info.annotation): + for entry in get_args(field_info.annotation): + if isinstance(entry, str): + mapping[entry] = variant + + if not mapping: + return None + + details = DiscriminatorDetails( + mapping=mapping, + discriminator_field=discriminator_field_name, + discriminator_alias=discriminator_alias, + ) + cast(CachedDiscriminatorType, union).__discriminator__ = details + return details + + +def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: + schema = model.__pydantic_core_schema__ + if schema["type"] != "model": + return None + + fields_schema = schema["schema"] + if fields_schema["type"] != "model-fields": + return None + + fields_schema = cast("ModelFieldsSchema", fields_schema) + + field = fields_schema["fields"].get(field_name) + if not field: + return None + + return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast] + + def validate_type(*, type_: type[_T], value: object) -> _T: """Strict validation that the given value matches the expected type""" if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel): diff --git a/src/lithic/_utils/_transform.py b/src/lithic/_utils/_transform.py index 1bd1330c..47e262a5 100644 --- a/src/lithic/_utils/_transform.py +++ b/src/lithic/_utils/_transform.py @@ -51,6 +51,7 @@ class MyParams(TypedDict): alias: str | None format: PropertyFormat | None format_template: str | None + discriminator: str | None def __init__( self, @@ -58,14 +59,16 @@ def __init__( alias: str | None = None, format: PropertyFormat | None = None, format_template: str | None = None, + discriminator: str | None = None, ) -> None: self.alias = alias self.format = format self.format_template = format_template + self.discriminator = discriminator @override def __repr__(self) -> str: - return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}')" + return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')" def maybe_transform( diff --git a/tests/test_models.py b/tests/test_models.py index 08fe1463..a0912ef5 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -7,6 +7,7 @@ import pydantic from pydantic import Field +from lithic._utils import PropertyInfo from lithic._compat import PYDANTIC_V2, parse_obj, model_dump, model_json from lithic._models import BaseModel, construct_type @@ -583,3 +584,182 @@ class Model(BaseModel): ) assert isinstance(m, Model) assert m.value == "foo" + + +def test_discriminated_unions_invalid_data() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "a", "data": 100}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, A) + assert m.type == "a" + if PYDANTIC_V2: + assert m.data == 100 # type: ignore[comparison-overlap] + else: + # pydantic v1 automatically converts inputs to strings + # if the expected type is a str + assert m.data == "100" + + +def test_discriminated_unions_unknown_variant() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + m = construct_type( + value={"type": "c", "data": None, "new_thing": "bar"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + + # just chooses the first variant + assert isinstance(m, A) + assert m.type == "c" # type: ignore[comparison-overlap] + assert m.data == None # type: ignore[unreachable] + assert m.new_thing == "bar" + + +def test_discriminated_unions_invalid_data_nested_unions() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + class C(BaseModel): + type: Literal["c"] + + data: bool + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "c", "data": "foo"}, + type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, C) + assert m.type == "c" + assert m.data == "foo" # type: ignore[comparison-overlap] + + +def test_discriminated_unions_with_aliases_invalid_data() -> None: + class A(BaseModel): + foo_type: Literal["a"] = Field(alias="type") + + data: str + + class B(BaseModel): + foo_type: Literal["b"] = Field(alias="type") + + data: int + + m = construct_type( + value={"type": "b", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), + ) + assert isinstance(m, B) + assert m.foo_type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + m = construct_type( + value={"type": "a", "data": 100}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]), + ) + assert isinstance(m, A) + assert m.foo_type == "a" + if PYDANTIC_V2: + assert m.data == 100 # type: ignore[comparison-overlap] + else: + # pydantic v1 automatically converts inputs to strings + # if the expected type is a str + assert m.data == "100" + + +def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["a"] + + data: int + + m = construct_type( + value={"type": "a", "data": "foo"}, + type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]), + ) + assert isinstance(m, B) + assert m.type == "a" + assert m.data == "foo" # type: ignore[comparison-overlap] + + +def test_discriminated_unions_invalid_data_uses_cache() -> None: + class A(BaseModel): + type: Literal["a"] + + data: str + + class B(BaseModel): + type: Literal["b"] + + data: int + + UnionType = cast(Any, Union[A, B]) + + assert not hasattr(UnionType, "__discriminator__") + + m = construct_type( + value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + discriminator = UnionType.__discriminator__ + assert discriminator is not None + + m = construct_type( + value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) + ) + assert isinstance(m, B) + assert m.type == "b" + assert m.data == "foo" # type: ignore[comparison-overlap] + + # if the discriminator details object stays the same between invocations then + # we hit the cache + assert UnionType.__discriminator__ is discriminator From 93530fdc4b004c6ec353a550b9641e37a898c1ef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:20:57 +0000 Subject: [PATCH 12/26] chore(docs): temporarily remove custom readme code (#387) --- README.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/README.md b/README.md index 41b0e96a..2fdea630 100644 --- a/README.md +++ b/README.md @@ -159,29 +159,6 @@ card = client.cards.create( print(card.product_id) ``` -## Webhook Verification - -We provide helper methods for verifying that a webhook request came from Lithic, and not a malicious third party. - -You can use `lithic.webhooks.verify_signature(body: string, headers, secret?) -> None` or `lithic.webhooks.unwrap(body: string, headers, secret?) -> Payload`, -both of which will raise an error if the signature is invalid. - -Note that the "body" parameter must be the raw JSON string sent from the server (do not parse it first). -The `.unwrap()` method can parse this JSON for you into a `Payload` object. - -For example, in [FastAPI](https://fastapi.tiangolo.com/): - -```py -@app.post('/my-webhook-handler') -async def handler(request: Request): - body = await request.body() - secret = os.environ['LITHIC_WEBHOOK_SECRET'] # env var used by default; explicit here. - payload = client.webhooks.unwrap(body, request.headers, secret) - print(payload) - - return {'ok': True} -``` - ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `lithic.APIConnectionError` is raised. From 23a52b28bcab6293ff4b3a212dbc389026c62a08 Mon Sep 17 00:00:00 2001 From: Eric Morphis Date: Wed, 13 Mar 2024 18:21:31 -0400 Subject: [PATCH 13/26] chore(docs): add back custom readme code This reverts commit 93530fdc4b004c6ec353a550b9641e37a898c1ef. --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 2fdea630..41b0e96a 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,29 @@ card = client.cards.create( print(card.product_id) ``` +## Webhook Verification + +We provide helper methods for verifying that a webhook request came from Lithic, and not a malicious third party. + +You can use `lithic.webhooks.verify_signature(body: string, headers, secret?) -> None` or `lithic.webhooks.unwrap(body: string, headers, secret?) -> Payload`, +both of which will raise an error if the signature is invalid. + +Note that the "body" parameter must be the raw JSON string sent from the server (do not parse it first). +The `.unwrap()` method can parse this JSON for you into a `Payload` object. + +For example, in [FastAPI](https://fastapi.tiangolo.com/): + +```py +@app.post('/my-webhook-handler') +async def handler(request: Request): + body = await request.body() + secret = os.environ['LITHIC_WEBHOOK_SECRET'] # env var used by default; explicit here. + payload = client.webhooks.unwrap(body, request.headers, secret) + print(payload) + + return {'ok': True} +``` + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `lithic.APIConnectionError` is raised. From 644a601cf2c271e5e3c3aff0a5084f14b0e1fb74 Mon Sep 17 00:00:00 2001 From: stainless-app Date: Thu, 14 Mar 2024 14:08:55 -0400 Subject: [PATCH 14/26] chore: temporarily remove examples for migration --- examples/.keep | 4 ---- examples/datetime_usage.py | 25 ------------------------- examples/hello_sailor.txt | 1 - examples/hello_world.txt | 1 - examples/upload_evidence.py | 34 ---------------------------------- 5 files changed, 65 deletions(-) delete mode 100644 examples/.keep delete mode 100644 examples/datetime_usage.py delete mode 100644 examples/hello_sailor.txt delete mode 100644 examples/hello_world.txt delete mode 100644 examples/upload_evidence.py diff --git a/examples/.keep b/examples/.keep deleted file mode 100644 index d8c73e93..00000000 --- a/examples/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store example files demonstrating usage of this SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/examples/datetime_usage.py b/examples/datetime_usage.py deleted file mode 100644 index a01df811..00000000 --- a/examples/datetime_usage.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env -S poetry run python - -from datetime import datetime - -from lithic import Lithic - -client = Lithic(environment="sandbox") - -now = datetime.now() - -# datetime responses will always be instances of `datetime` -card = client.cards.create(type="VIRTUAL") -assert isinstance(card.created, datetime) -assert card.created.year == now.year -assert card.created.month == now.month -assert card.created.tzname() == "UTC" - -dt = datetime.fromisoformat("2022-07-25T21:34:45+00:00") - -# # both `datetime` instances or datetime strings can be passed as a request param -page = client.cards.list(begin=dt, page_size=1) -assert len(page.data) == 1 - -page = client.cards.list(begin=dt.isoformat(), page_size=1) -assert len(page.data) == 1 diff --git a/examples/hello_sailor.txt b/examples/hello_sailor.txt deleted file mode 100644 index cc034734..00000000 --- a/examples/hello_sailor.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, Sailor! diff --git a/examples/hello_world.txt b/examples/hello_world.txt deleted file mode 100644 index 8ab686ea..00000000 --- a/examples/hello_world.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, World! diff --git a/examples/upload_evidence.py b/examples/upload_evidence.py deleted file mode 100644 index 440a1666..00000000 --- a/examples/upload_evidence.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env -S poetry run python - -# -# Run with: LITHIC_API_KEY= poetry run python examples/upload_evidence.py -# - -from lithic import Lithic, file_from_path - -client = Lithic(environment="sandbox") - -transactions_page = client.transactions.list() -assert len(transactions_page.data) > 0, "No transactions found" - -transaction = transactions_page.data[0] -assert transaction.token, "Transaction must have a token" - -disputes_page = client.disputes.list() -dispute = disputes_page.data[0] -if not dispute: - dispute = client.disputes.create( - amount=42, - reason="ATM_CASH_MISDISPENSE", - transaction_token=transaction.token, - ) - -print(dispute) -assert dispute, "Could not find or create a dispute" - -my_file = file_from_path("hello_world.txt") - -upload = client.disputes.upload_evidence(dispute.token, my_file) -print(upload) - -print("Done!") From e0eba7230c6256a2b90ea1de7a9e814f80da4398 Mon Sep 17 00:00:00 2001 From: stainless-app Date: Thu, 14 Mar 2024 14:14:24 -0400 Subject: [PATCH 15/26] chore: add back examples This reverts commit 644a601cf2c271e5e3c3aff0a5084f14b0e1fb74. --- examples/.keep | 4 ++++ examples/datetime_usage.py | 25 +++++++++++++++++++++++++ examples/hello_sailor.txt | 1 + examples/hello_world.txt | 1 + examples/upload_evidence.py | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+) create mode 100644 examples/.keep create mode 100644 examples/datetime_usage.py create mode 100644 examples/hello_sailor.txt create mode 100644 examples/hello_world.txt create mode 100644 examples/upload_evidence.py diff --git a/examples/.keep b/examples/.keep new file mode 100644 index 00000000..d8c73e93 --- /dev/null +++ b/examples/.keep @@ -0,0 +1,4 @@ +File generated from our OpenAPI spec by Stainless. + +This directory can be used to store example files demonstrating usage of this SDK. +It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/examples/datetime_usage.py b/examples/datetime_usage.py new file mode 100644 index 00000000..a01df811 --- /dev/null +++ b/examples/datetime_usage.py @@ -0,0 +1,25 @@ +#!/usr/bin/env -S poetry run python + +from datetime import datetime + +from lithic import Lithic + +client = Lithic(environment="sandbox") + +now = datetime.now() + +# datetime responses will always be instances of `datetime` +card = client.cards.create(type="VIRTUAL") +assert isinstance(card.created, datetime) +assert card.created.year == now.year +assert card.created.month == now.month +assert card.created.tzname() == "UTC" + +dt = datetime.fromisoformat("2022-07-25T21:34:45+00:00") + +# # both `datetime` instances or datetime strings can be passed as a request param +page = client.cards.list(begin=dt, page_size=1) +assert len(page.data) == 1 + +page = client.cards.list(begin=dt.isoformat(), page_size=1) +assert len(page.data) == 1 diff --git a/examples/hello_sailor.txt b/examples/hello_sailor.txt new file mode 100644 index 00000000..cc034734 --- /dev/null +++ b/examples/hello_sailor.txt @@ -0,0 +1 @@ +Hello, Sailor! diff --git a/examples/hello_world.txt b/examples/hello_world.txt new file mode 100644 index 00000000..8ab686ea --- /dev/null +++ b/examples/hello_world.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/examples/upload_evidence.py b/examples/upload_evidence.py new file mode 100644 index 00000000..440a1666 --- /dev/null +++ b/examples/upload_evidence.py @@ -0,0 +1,34 @@ +#!/usr/bin/env -S poetry run python + +# +# Run with: LITHIC_API_KEY= poetry run python examples/upload_evidence.py +# + +from lithic import Lithic, file_from_path + +client = Lithic(environment="sandbox") + +transactions_page = client.transactions.list() +assert len(transactions_page.data) > 0, "No transactions found" + +transaction = transactions_page.data[0] +assert transaction.token, "Transaction must have a token" + +disputes_page = client.disputes.list() +dispute = disputes_page.data[0] +if not dispute: + dispute = client.disputes.create( + amount=42, + reason="ATM_CASH_MISDISPENSE", + transaction_token=transaction.token, + ) + +print(dispute) +assert dispute, "Could not find or create a dispute" + +my_file = file_from_path("hello_world.txt") + +upload = client.disputes.upload_evidence(dispute.token, my_file) +print(upload) + +print("Done!") From 306df733bf153bf89931060cf2c5d9aa170c86ed Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 19:46:14 +0000 Subject: [PATCH 16/26] chore: temporarily remove various code as part of refactor (#388) --- api.md | 11 - integrations/pagination.py | 32 --- src/lithic/_client.py | 4 - src/lithic/resources/__init__.py | 3 - src/lithic/resources/cards/cards.py | 232 +----------------- src/lithic/resources/disputes.py | 55 +---- src/lithic/resources/events/events.py | 41 +--- src/lithic/resources/webhooks.py | 207 ---------------- src/lithic/types/__init__.py | 3 - .../types/card_get_embed_html_params.py | 41 ---- src/lithic/types/card_get_embed_url_params.py | 41 ---- src/lithic/types/event_resend_params.py | 41 ---- tests/api_resources/test_cards.py | 24 -- tests/api_resources/test_events.py | 14 -- tests/api_resources/test_webhooks.py | 211 ---------------- 15 files changed, 4 insertions(+), 956 deletions(-) delete mode 100755 integrations/pagination.py delete mode 100644 src/lithic/resources/webhooks.py delete mode 100644 src/lithic/types/card_get_embed_html_params.py delete mode 100644 src/lithic/types/card_get_embed_url_params.py delete mode 100644 src/lithic/types/event_resend_params.py delete mode 100644 tests/api_resources/test_webhooks.py diff --git a/api.md b/api.md index 21359f56..11bd283d 100644 --- a/api.md +++ b/api.md @@ -150,8 +150,6 @@ Methods: - client.cards.renew(card_token, \*\*params) -> Card - client.cards.retrieve_spend_limits(card_token) -> CardSpendLimits - client.cards.search_by_pan(\*\*params) -> Card -- client.cards.get_embed_html(\*args) -> str -- client.cards.get_embed_url(\*args) -> URL ## AggregateBalances @@ -221,7 +219,6 @@ Methods: - client.disputes.initiate_evidence_upload(dispute_token, \*\*params) -> DisputeEvidence - client.disputes.list_evidences(dispute_token, \*\*params) -> SyncCursorPage[DisputeEvidence] - client.disputes.retrieve_evidence(evidence_token, \*, dispute_token) -> DisputeEvidence -- client.disputes.upload_evidence(\*args) -> None # Events @@ -236,7 +233,6 @@ Methods: - client.events.retrieve(event_token) -> Event - client.events.list(\*\*params) -> SyncCursorPage[Event] - client.events.list_attempts(event_token, \*\*params) -> SyncCursorPage[MessageAttempt] -- client.events.resend(\*args) -> None ## Subscriptions @@ -356,13 +352,6 @@ Methods: - client.responder_endpoints.delete(\*\*params) -> None - client.responder_endpoints.check_status(\*\*params) -> ResponderEndpointStatus -# Webhooks - -Methods: - -- client.webhooks.unwrap(\*args) -> object -- client.webhooks.verify_signature(\*args) -> None - # ExternalBankAccounts Types: diff --git a/integrations/pagination.py b/integrations/pagination.py deleted file mode 100755 index e874414f..00000000 --- a/integrations/pagination.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env -S rye run python integrations/pagination.py - -from __future__ import annotations - -import json - -from lithic import Lithic - -client = Lithic(environment="sandbox") - - -def main() -> None: - page = client.transactions.list() - assert len(page.data) > 0, "No transactions found" - - if not page.has_more or not page.has_next_page(): - raise RuntimeError(f"Expected multiple pages to be present, only got {len(page.data)} items") - - tokens: dict[str, int] = {} - - for transaction in page: - tokens[transaction.token] = tokens.get(transaction.token, 0) + 1 - - duplicates = {token: count for token, count in tokens.items() if count > 1} - if duplicates: - print(json.dumps(duplicates, indent=2)) # noqa: T201 - raise RuntimeError(f"Found {len(duplicates)} duplicate entries!") - - print("Success!") # noqa: T201 - - -main() diff --git a/src/lithic/_client.py b/src/lithic/_client.py index 3c0fda1a..dd58984a 100644 --- a/src/lithic/_client.py +++ b/src/lithic/_client.py @@ -76,7 +76,6 @@ class Lithic(SyncAPIClient): financial_accounts: resources.FinancialAccounts transactions: resources.Transactions responder_endpoints: resources.ResponderEndpoints - webhooks: resources.Webhooks external_bank_accounts: resources.ExternalBankAccounts payments: resources.Payments three_ds: resources.ThreeDS @@ -194,7 +193,6 @@ def __init__( self.financial_accounts = resources.FinancialAccounts(self) self.transactions = resources.Transactions(self) self.responder_endpoints = resources.ResponderEndpoints(self) - self.webhooks = resources.Webhooks(self) self.external_bank_accounts = resources.ExternalBankAccounts(self) self.payments = resources.Payments(self) self.three_ds = resources.ThreeDS(self) @@ -368,7 +366,6 @@ class AsyncLithic(AsyncAPIClient): financial_accounts: resources.AsyncFinancialAccounts transactions: resources.AsyncTransactions responder_endpoints: resources.AsyncResponderEndpoints - webhooks: resources.AsyncWebhooks external_bank_accounts: resources.AsyncExternalBankAccounts payments: resources.AsyncPayments three_ds: resources.AsyncThreeDS @@ -486,7 +483,6 @@ def __init__( self.financial_accounts = resources.AsyncFinancialAccounts(self) self.transactions = resources.AsyncTransactions(self) self.responder_endpoints = resources.AsyncResponderEndpoints(self) - self.webhooks = resources.AsyncWebhooks(self) self.external_bank_accounts = resources.AsyncExternalBankAccounts(self) self.payments = resources.AsyncPayments(self) self.three_ds = resources.AsyncThreeDS(self) diff --git a/src/lithic/resources/__init__.py b/src/lithic/resources/__init__.py index aabd710d..cb865d98 100644 --- a/src/lithic/resources/__init__.py +++ b/src/lithic/resources/__init__.py @@ -64,7 +64,6 @@ ThreeDSWithStreamingResponse, AsyncThreeDSWithStreamingResponse, ) -from .webhooks import Webhooks, AsyncWebhooks from .auth_rules import ( AuthRules, AsyncAuthRules, @@ -255,8 +254,6 @@ "AsyncResponderEndpointsWithRawResponse", "ResponderEndpointsWithStreamingResponse", "AsyncResponderEndpointsWithStreamingResponse", - "Webhooks", - "AsyncWebhooks", "ExternalBankAccounts", "AsyncExternalBankAccounts", "ExternalBankAccountsWithRawResponse", diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index c56972fb..ac1cbf44 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -2,16 +2,11 @@ from __future__ import annotations -import hmac -import json -import base64 -import hashlib from typing import Union -from datetime import datetime, timezone, timedelta +from datetime import datetime from typing_extensions import Literal import httpx -from httpx import URL from ... import _legacy_response from ...types import ( @@ -27,7 +22,6 @@ card_update_params, card_reissue_params, card_provision_params, - card_get_embed_url_params, card_search_by_pan_params, ) from ..._types import ( @@ -40,7 +34,6 @@ ) from ..._utils import ( maybe_transform, - strip_not_given, async_maybe_transform, ) from .balances import ( @@ -57,7 +50,6 @@ from ...pagination import SyncCursorPage, AsyncCursorPage from ..._base_client import ( AsyncPaginator, - _merge_mappings, make_request_options, ) from .aggregate_balances import ( @@ -541,117 +533,6 @@ def embed( cast_to=str, ) - def get_embed_html( - self, - *, - token: str, - css: str | NotGiven = NOT_GIVEN, - expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, - target_origin: str | NotGiven = NOT_GIVEN, - # 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, - ) -> str: - """ - Generates and executes an embed request, returning html you can serve to the - user. - - Be aware that this html contains sensitive data whose presence on your server - could trigger PCI DSS. - - If your company is not certified PCI compliant, we recommend using - `get_embed_url()` instead. You would then pass that returned URL to the - frontend, where you can load it via an iframe. - """ - headers = _merge_mappings({"Accept": "text/html"}, extra_headers or {}) - url = self.get_embed_url( - css=css, - token=token, - expiration=expiration, - target_origin=target_origin, - ) - return self._get( - str(url), - cast_to=str, - options=make_request_options( - extra_headers=headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ), - ) - - def get_embed_url( - self, - *, - token: str, - css: str | NotGiven = NOT_GIVEN, - expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, - target_origin: str | NotGiven = NOT_GIVEN, - ) -> URL: - """ - Handling full card PANs and CVV codes requires that you comply with the Payment - Card Industry Data Security Standards (PCI DSS). Some clients choose to reduce - their compliance obligations by leveraging our embedded card UI solution - documented below. - - In this setup, PANs and CVV codes are presented to the end-user via a card UI - that we provide, optionally styled in the customer's branding using a specified - css stylesheet. A user's browser makes the request directly to api.lithic.com, - so card PANs and CVVs never touch the API customer's servers while full card - data is displayed to their end-users. The response contains an HTML document. - This means that the url for the request can be inserted straight into the `src` - attribute of an iframe. - - ```html - - ``` - - You should compute the request payload on the server side. You can render it (or - the whole iframe) on the server or make an ajax call from your front end code, - but **do not ever embed your API key into front end code, as doing so introduces - a serious security vulnerability**. - """ - # Default expiration of 1 minute from now. - if isinstance(expiration, NotGiven): - expiration = datetime.now(timezone.utc) + timedelta(minutes=1) - - query = maybe_transform( - strip_not_given( - { - "css": css, - "token": token, - "expiration": expiration, - "target_origin": target_origin, - } - ), - card_get_embed_url_params.CardGetEmbedURLParams, - ) - serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) - params = { - "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), - "hmac": base64.b64encode( - hmac.new( - key=bytes(self._client.api_key, "utf-8"), - msg=bytes(serialized, "utf-8"), - digestmod=hashlib.sha256, - ).digest() - ).decode("utf-8"), - } - - # Copied nearly directly from httpx.BaseClient._merge_url - base_url = self._client.base_url - raw_path = base_url.raw_path + URL("embed/card").raw_path - return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) - def provision( self, card_token: str, @@ -1405,117 +1286,6 @@ async def embed( cast_to=str, ) - async def get_embed_html( - self, - *, - token: str, - css: str | NotGiven = NOT_GIVEN, - expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, - target_origin: str | NotGiven = NOT_GIVEN, - # 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, - ) -> str: - """ - Generates and executes an embed request, returning html you can serve to the - user. - - Be aware that this html contains sensitive data whose presence on your server - could trigger PCI DSS. - - If your company is not certified PCI compliant, we recommend using - `get_embed_url()` instead. You would then pass that returned URL to the - frontend, where you can load it via an iframe. - """ - headers = _merge_mappings({"Accept": "text/html"}, extra_headers or {}) - url = self.get_embed_url( - css=css, - token=token, - expiration=expiration, - target_origin=target_origin, - ) - return await self._get( - str(url), - cast_to=str, - options=make_request_options( - extra_headers=headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - ), - ) - - def get_embed_url( - self, - *, - token: str, - css: str | NotGiven = NOT_GIVEN, - expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, - target_origin: str | NotGiven = NOT_GIVEN, - ) -> URL: - """ - Handling full card PANs and CVV codes requires that you comply with the Payment - Card Industry Data Security Standards (PCI DSS). Some clients choose to reduce - their compliance obligations by leveraging our embedded card UI solution - documented below. - - In this setup, PANs and CVV codes are presented to the end-user via a card UI - that we provide, optionally styled in the customer's branding using a specified - css stylesheet. A user's browser makes the request directly to api.lithic.com, - so card PANs and CVVs never touch the API customer's servers while full card - data is displayed to their end-users. The response contains an HTML document. - This means that the url for the request can be inserted straight into the `src` - attribute of an iframe. - - ```html - - ``` - - You should compute the request payload on the server side. You can render it (or - the whole iframe) on the server or make an ajax call from your front end code, - but **do not ever embed your API key into front end code, as doing so introduces - a serious security vulnerability**. - """ - # Default expiration of 1 minute from now. - if isinstance(expiration, NotGiven): - expiration = datetime.now(timezone.utc) + timedelta(minutes=1) - - query = maybe_transform( - strip_not_given( - { - "css": css, - "token": token, - "expiration": expiration, - "target_origin": target_origin, - } - ), - card_get_embed_url_params.CardGetEmbedURLParams, - ) - serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) - params = { - "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), - "hmac": base64.b64encode( - hmac.new( - key=bytes(self._client.api_key, "utf-8"), - msg=bytes(serialized, "utf-8"), - digestmod=hashlib.sha256, - ).digest() - ).decode("utf-8"), - } - - # Copied nearly directly from httpx.BaseClient._merge_url - base_url = self._client.base_url - raw_path = base_url.raw_path + URL("embed/card").raw_path - return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) - async def provision( self, card_token: str, diff --git a/src/lithic/resources/disputes.py b/src/lithic/resources/disputes.py index ef613e18..d2105ccc 100644 --- a/src/lithic/resources/disputes.py +++ b/src/lithic/resources/disputes.py @@ -18,16 +18,7 @@ dispute_list_evidences_params, dispute_initiate_evidence_upload_params, ) -from .._types import ( - NOT_GIVEN, - Body, - Omit, - Query, - Headers, - NoneType, - NotGiven, - FileTypes, -) +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import ( maybe_transform, async_maybe_transform, @@ -526,28 +517,6 @@ def retrieve_evidence( cast_to=DisputeEvidence, ) - def upload_evidence( - self, - dispute_token: str, - file: FileTypes, - *, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """ - Initiates the Dispute Evidence Upload, then uploads the file to the returned - `upload_url`. - """ - payload = self._client.disputes.initiate_evidence_upload( - dispute_token, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body - ) - if not payload.upload_url: - raise ValueError("Missing 'upload_url' from response payload") - files = {"file": file} - options = make_request_options(extra_headers={"Authorization": Omit()}) - self._put(payload.upload_url, cast_to=NoneType, body=None, files=files, options=options) - class AsyncDisputes(AsyncAPIResource): @cached_property @@ -1031,28 +1000,6 @@ async def retrieve_evidence( cast_to=DisputeEvidence, ) - async def upload_evidence( - self, - dispute_token: str, - file: FileTypes, - *, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """ - Initiates the Dispute Evidence Upload, then uploads the file to the returned - `upload_url`. - """ - payload = await self._client.disputes.initiate_evidence_upload( - dispute_token, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body - ) - if not payload.upload_url: - raise ValueError("Missing 'upload_url' from response payload") - files = {"file": file} - options = make_request_options(extra_headers={"Authorization": Omit()}) - await self._put(payload.upload_url, cast_to=NoneType, body=None, files=files, options=options) - class DisputesWithRawResponse: def __init__(self, disputes: Disputes) -> None: diff --git a/src/lithic/resources/events/events.py b/src/lithic/resources/events/events.py index 76bb29e4..a9d22cdb 100644 --- a/src/lithic/resources/events/events.py +++ b/src/lithic/resources/events/events.py @@ -9,13 +9,8 @@ import httpx from ... import _legacy_response -from ...types import ( - Event, - MessageAttempt, - event_list_params, - event_list_attempts_params, -) -from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from ...types import Event, MessageAttempt, event_list_params, event_list_attempts_params +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -244,22 +239,6 @@ def list_attempts( model=MessageAttempt, ) - def resend( - self, - event_token: str, - *, - event_subscription_token: str, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """Resend an event to an event subscription.""" - self._post( - f"/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", - options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body), - cast_to=NoneType, - ) - class AsyncEvents(AsyncAPIResource): @cached_property @@ -468,22 +447,6 @@ def list_attempts( model=MessageAttempt, ) - async def resend( - self, - event_token: str, - *, - event_subscription_token: str, - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - ) -> None: - """Resend an event to an event subscription.""" - await self._post( - f"/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", - options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body), - cast_to=NoneType, - ) - class EventsWithRawResponse: def __init__(self, events: Events) -> None: diff --git a/src/lithic/resources/webhooks.py b/src/lithic/resources/webhooks.py deleted file mode 100644 index 8b5094f8..00000000 --- a/src/lithic/resources/webhooks.py +++ /dev/null @@ -1,207 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import hmac -import json -import math -import base64 -import hashlib -from datetime import datetime, timezone, timedelta - -from .._types import ( - HeadersLike, -) -from .._utils import ( - removeprefix, - get_required_header, -) -from .._resource import SyncAPIResource, AsyncAPIResource - -__all__ = ["Webhooks", "AsyncWebhooks"] - - -class Webhooks(SyncAPIResource): - def unwrap( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> object: - """Validates that the given payload was sent by Lithic and parses the payload.""" - self.verify_signature(payload=payload, headers=headers, secret=secret) - return json.loads(payload) - - def verify_signature( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> None: - """Validates whether or not the webhook payload was sent by Lithic. - - An error will be raised if the webhook payload was not sent by Lithic. - """ - if secret is None: - secret = self._client.webhook_secret - - if secret is None: - raise ValueError( - "The webhook secret must either be set using the env var, LITHIC_WEBHOOK_SECRET, on the client class, Lithic(webhook_secret='123'), or passed to this function" - ) - - try: - whsecret = base64.b64decode(removeprefix(secret, "whsec_")) - except Exception as err: - raise ValueError("Bad secret") from err - - msg_id = get_required_header(headers, "webhook-id") - msg_timestamp = get_required_header(headers, "webhook-timestamp") - - # validate the timestamp - webhook_tolerance = timedelta(minutes=5) - now = datetime.now(tz=timezone.utc) - - try: - timestamp = datetime.fromtimestamp(float(msg_timestamp), tz=timezone.utc) - except Exception as err: - raise ValueError("Invalid signature headers. Could not convert to timestamp") from err - - # too old - if timestamp < (now - webhook_tolerance): - raise ValueError("Webhook timestamp is too old") - - # too new - if timestamp > (now + webhook_tolerance): - raise ValueError("Webhook timestamp is too new") - - # create the signature - body = payload.decode("utf-8") if isinstance(payload, bytes) else payload - if not isinstance(body, str): # pyright: ignore[reportUnnecessaryIsInstance] - raise ValueError( - "Webhook body should be a string of JSON (or bytes which can be decoded to a utf-8 string), not a parsed dictionary." - ) - - timestamp_str = str(math.floor(timestamp.replace(tzinfo=timezone.utc).timestamp())) - - to_sign = f"{msg_id}.{timestamp_str}.{body}".encode() - expected_signature = hmac.new(whsecret, to_sign, hashlib.sha256).digest() - - msg_signature = get_required_header(headers, "webhook-signature") - - # Signature header can contain multiple signatures delimited by spaces - passed_sigs = msg_signature.split(" ") - - for versioned_sig in passed_sigs: - values = versioned_sig.split(",") - if len(values) != 2: - # signature is not formatted like {version},{signature} - continue - - (version, signature) = values - - # Only verify prefix v1 - if version != "v1": - continue - - sig_bytes = base64.b64decode(signature) - if hmac.compare_digest(expected_signature, sig_bytes): - # valid! - return None - - raise ValueError("None of the given webhook signatures match the expected signature") - - -class AsyncWebhooks(AsyncAPIResource): - def unwrap( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> object: - """Validates that the given payload was sent by Lithic and parses the payload.""" - self.verify_signature(payload=payload, headers=headers, secret=secret) - return json.loads(payload) - - def verify_signature( - self, - payload: str | bytes, - headers: HeadersLike, - *, - secret: str | None = None, - ) -> None: - """Validates whether or not the webhook payload was sent by Lithic. - - An error will be raised if the webhook payload was not sent by Lithic. - """ - if secret is None: - secret = self._client.webhook_secret - - if secret is None: - raise ValueError( - "The webhook secret must either be set using the env var, LITHIC_WEBHOOK_SECRET, on the client class, Lithic(webhook_secret='123'), or passed to this function" - ) - - try: - whsecret = base64.b64decode(removeprefix(secret, "whsec_")) - except Exception as err: - raise ValueError("Bad secret") from err - - msg_id = get_required_header(headers, "webhook-id") - msg_timestamp = get_required_header(headers, "webhook-timestamp") - - # validate the timestamp - webhook_tolerance = timedelta(minutes=5) - now = datetime.now(tz=timezone.utc) - - try: - timestamp = datetime.fromtimestamp(float(msg_timestamp), tz=timezone.utc) - except Exception as err: - raise ValueError("Invalid signature headers. Could not convert to timestamp") from err - - # too old - if timestamp < (now - webhook_tolerance): - raise ValueError("Webhook timestamp is too old") - - # too new - if timestamp > (now + webhook_tolerance): - raise ValueError("Webhook timestamp is too new") - - # create the signature - body = payload.decode("utf-8") if isinstance(payload, bytes) else payload - if not isinstance(body, str): # pyright: ignore[reportUnnecessaryIsInstance] - raise ValueError( - "Webhook body should be a string of JSON (or bytes which can be decoded to a utf-8 string), not a parsed dictionary." - ) - - timestamp_str = str(math.floor(timestamp.replace(tzinfo=timezone.utc).timestamp())) - - to_sign = f"{msg_id}.{timestamp_str}.{body}".encode() - expected_signature = hmac.new(whsecret, to_sign, hashlib.sha256).digest() - - msg_signature = get_required_header(headers, "webhook-signature") - - # Signature header can contain multiple signatures delimited by spaces - passed_sigs = msg_signature.split(" ") - - for versioned_sig in passed_sigs: - values = versioned_sig.split(",") - if len(values) != 2: - # signature is not formatted like {version},{signature} - continue - - (version, signature) = values - - # Only verify prefix v1 - if version != "v1": - continue - - sig_bytes = base64.b64decode(signature) - if hmac.compare_digest(expected_signature, sig_bytes): - # valid! - return None - - raise ValueError("None of the given webhook signatures match the expected signature") diff --git a/src/lithic/types/__init__.py b/src/lithic/types/__init__.py index bc1b39f6..fce1f10f 100644 --- a/src/lithic/types/__init__.py +++ b/src/lithic/types/__init__.py @@ -38,7 +38,6 @@ from .card_embed_response import CardEmbedResponse as CardEmbedResponse from .card_reissue_params import CardReissueParams as CardReissueParams from .dispute_list_params import DisputeListParams as DisputeListParams -from .event_resend_params import EventResendParams as EventResendParams from .payment_list_params import PaymentListParams as PaymentListParams from .tokenization_secret import TokenizationSecret as TokenizationSecret from .verification_method import VerificationMethod as VerificationMethod @@ -63,11 +62,9 @@ from .card_program_list_params import CardProgramListParams as CardProgramListParams from .tokenization_list_params import TokenizationListParams as TokenizationListParams from .auth_rule_remove_response import AuthRuleRemoveResponse as AuthRuleRemoveResponse -from .card_get_embed_url_params import CardGetEmbedURLParams as CardGetEmbedURLParams from .card_search_by_pan_params import CardSearchByPanParams as CardSearchByPanParams from .responder_endpoint_status import ResponderEndpointStatus as ResponderEndpointStatus from .account_holder_list_params import AccountHolderListParams as AccountHolderListParams -from .card_get_embed_html_params import CardGetEmbedHTMLParams as CardGetEmbedHTMLParams from .event_list_attempts_params import EventListAttemptsParams as EventListAttemptsParams from .settlement_summary_details import SettlementSummaryDetails as SettlementSummaryDetails from .auth_rule_retrieve_response import AuthRuleRetrieveResponse as AuthRuleRetrieveResponse diff --git a/src/lithic/types/card_get_embed_html_params.py b/src/lithic/types/card_get_embed_html_params.py deleted file mode 100644 index 86bb86d3..00000000 --- a/src/lithic/types/card_get_embed_html_params.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["CardGetEmbedHTMLParams"] - - -class CardGetEmbedHTMLParams(TypedDict, total=False): - token: Required[str] - """Globally unique identifier for the card to be displayed.""" - - css: str - """ - A publicly available URI, so the white-labeled card element can be styled with - the client's branding. - """ - - expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """An RFC 3339 timestamp for when the request should expire. UTC time zone. - - If no timezone is specified, UTC will be used. If payload does not contain an - expiration, the request will never expire. - - Using an `expiration` reduces the risk of a - [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying - the `expiration`, in the event that a malicious user gets a copy of your request - in transit, they will be able to obtain the response data indefinitely. - """ - - target_origin: str - """Required if you want to post the element clicked to the parent iframe. - - If you supply this param, you can also capture click events in the parent iframe - by adding an event listener. - """ diff --git a/src/lithic/types/card_get_embed_url_params.py b/src/lithic/types/card_get_embed_url_params.py deleted file mode 100644 index df77c553..00000000 --- a/src/lithic/types/card_get_embed_url_params.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["CardGetEmbedURLParams"] - - -class CardGetEmbedURLParams(TypedDict, total=False): - token: Required[str] - """Globally unique identifier for the card to be displayed.""" - - css: str - """ - A publicly available URI, so the white-labeled card element can be styled with - the client's branding. - """ - - expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """An RFC 3339 timestamp for when the request should expire. UTC time zone. - - If no timezone is specified, UTC will be used. If payload does not contain an - expiration, the request will never expire. - - Using an `expiration` reduces the risk of a - [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying - the `expiration`, in the event that a malicious user gets a copy of your request - in transit, they will be able to obtain the response data indefinitely. - """ - - target_origin: str - """Required if you want to post the element clicked to the parent iframe. - - If you supply this param, you can also capture click events in the parent iframe - by adding an event listener. - """ diff --git a/src/lithic/types/event_resend_params.py b/src/lithic/types/event_resend_params.py deleted file mode 100644 index e381da50..00000000 --- a/src/lithic/types/event_resend_params.py +++ /dev/null @@ -1,41 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -from typing import Union -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["EventResendParams"] - - -class EventResendParams(TypedDict, total=False): - token: Required[str] - """Globally unique identifier for the card to be displayed.""" - - css: str - """ - A publicly available URI, so the white-labeled card element can be styled with - the client's branding. - """ - - expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """An RFC 3339 timestamp for when the request should expire. UTC time zone. - - If no timezone is specified, UTC will be used. If payload does not contain an - expiration, the request will never expire. - - Using an `expiration` reduces the risk of a - [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying - the `expiration`, in the event that a malicious user gets a copy of your request - in transit, they will be able to obtain the response data indefinitely. - """ - - target_origin: str - """Required if you want to post the element clicked to the parent iframe. - - If you supply this param, you can also capture click events in the parent iframe - by adding an event listener. - """ diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index a01331e0..fa03d2ec 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -250,18 +250,6 @@ def test_streaming_response_embed(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True - def test_get_embed_html(self, client: Lithic) -> None: - html = client.cards.get_embed_html(token="foo") - assert "html" in html - - def test_get_embed_url(self, client: Lithic) -> None: - url = client.cards.get_embed_url(token="foo") - params = set( # pyright: ignore[reportUnknownVariableType] - url.params.keys() # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] - ) - assert "hmac" in params - assert "embed_request" in params - @parametrize def test_method_provision(self, client: Lithic) -> None: card = client.cards.provision( @@ -771,18 +759,6 @@ async def test_streaming_response_embed(self, async_client: AsyncLithic) -> None assert cast(Any, response.is_closed) is True - async def test_get_embed_html(self, async_client: AsyncLithic) -> None: - html = await async_client.cards.get_embed_html(token="foo") - assert "html" in html - - def test_get_embed_url(self, async_client: Lithic) -> None: - url = async_client.cards.get_embed_url(token="foo") - params = set( # pyright: ignore[reportUnknownVariableType] - url.params.keys() # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] - ) - assert "hmac" in params - assert "embed_request" in params - @parametrize async def test_method_provision(self, async_client: AsyncLithic) -> None: card = await async_client.cards.provision( diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py index 893dc631..267caa8c 100644 --- a/tests/api_resources/test_events.py +++ b/tests/api_resources/test_events.py @@ -146,13 +146,6 @@ def test_path_params_list_attempts(self, client: Lithic) -> None: "", ) - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") - def test_method_resend(self, client: Lithic) -> None: - client.events.resend( - "string", - event_subscription_token="string", - ) - class TestAsyncEvents: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -283,10 +276,3 @@ async def test_path_params_list_attempts(self, async_client: AsyncLithic) -> Non await async_client.events.with_raw_response.list_attempts( "", ) - - @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") - async def test_method_resend(self, async_client: AsyncLithic) -> None: - await async_client.events.resend( - "string", - event_subscription_token="string", - ) diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py deleted file mode 100644 index c3fc25b5..00000000 --- a/tests/api_resources/test_webhooks.py +++ /dev/null @@ -1,211 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os -import base64 -from typing import Any, cast -from datetime import datetime, timezone, timedelta - -import pytest -import time_machine - -from lithic import Lithic, AsyncLithic - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestWebhooks: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - timestamp = "1676312382" - fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) - - payload = """{"card_token":"sit Lorem ipsum, accusantium repellendus possimus","created_at":"elit. placeat libero architecto molestias, sit","account_token":"elit.","issuer_decision":"magnam, libero esse Lorem ipsum magnam, magnam,","tokenization_attempt_id":"illum dolor repellendus libero esse accusantium","wallet_decisioning_info":{"device_score":"placeat architecto"},"digital_wallet_token_metadata":{"status":"reprehenderit dolor","token_requestor_id":"possimus","payment_account_info":{"account_holder_data":{"phone_number":"libero","email_address":"nobis molestias, veniam culpa! quas elit. quas libero esse architecto placeat"},"pan_unique_reference":"adipisicing odit magnam, odit"}}}""" - signature = "Dwa0AHInLL3XFo2sxcHamOQDrJNi7F654S3L6skMAOI=" - headers = { - "webhook-id": "msg_2Lh9KRb0pzN4LePd3XiA4v12Axj", - "webhook-timestamp": timestamp, - "webhook-signature": f"v1,{signature}", - } - secret = "whsec_zlFsbBZ8Xcodlpcu6NDTdSzZRLSdhkst" - - @time_machine.travel(fake_now) - def test_unwrap(self, client: Lithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - - client.webhooks.unwrap(payload, headers, secret=secret) - - @time_machine.travel(fake_now) - def test_verify_signature(self, client: Lithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - signature = self.signature - verify = client.webhooks.verify_signature - - assert verify(payload=payload, headers=headers, secret=secret) is None - - with pytest.raises(ValueError, match="Webhook timestamp is too old"): - with time_machine.travel(self.fake_now + timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - with pytest.raises(ValueError, match="Webhook timestamp is too new"): - with time_machine.travel(self.fake_now - timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - # wrong secret - with pytest.raises(ValueError, match=r"Bad secret"): - verify(payload=payload, headers=headers, secret="invalid secret") - - invalid_signature_message = "None of the given webhook signatures match the expected signature" - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers=headers, - secret=f"whsec_{base64.b64encode(b'foo').decode('utf-8')}", - ) - - # multiple signatures - invalid_signature = base64.b64encode(b"my_sig").decode("utf-8") - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v1,{invalid_signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # different signature version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature}"}, - secret=secret, - ) - - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # missing version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": signature}, - secret=secret, - ) - - # non-string payload - with pytest.raises(ValueError, match=r"Webhook body should be a string"): - verify( - payload=cast(Any, {"payload": payload}), - headers=headers, - secret=secret, - ) - - -class TestAsyncWebhooks: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - timestamp = "1676312382" - fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) - - payload = """{"card_token":"sit Lorem ipsum, accusantium repellendus possimus","created_at":"elit. placeat libero architecto molestias, sit","account_token":"elit.","issuer_decision":"magnam, libero esse Lorem ipsum magnam, magnam,","tokenization_attempt_id":"illum dolor repellendus libero esse accusantium","wallet_decisioning_info":{"device_score":"placeat architecto"},"digital_wallet_token_metadata":{"status":"reprehenderit dolor","token_requestor_id":"possimus","payment_account_info":{"account_holder_data":{"phone_number":"libero","email_address":"nobis molestias, veniam culpa! quas elit. quas libero esse architecto placeat"},"pan_unique_reference":"adipisicing odit magnam, odit"}}}""" - signature = "Dwa0AHInLL3XFo2sxcHamOQDrJNi7F654S3L6skMAOI=" - headers = { - "webhook-id": "msg_2Lh9KRb0pzN4LePd3XiA4v12Axj", - "webhook-timestamp": timestamp, - "webhook-signature": f"v1,{signature}", - } - secret = "whsec_zlFsbBZ8Xcodlpcu6NDTdSzZRLSdhkst" - - @time_machine.travel(fake_now) - def test_unwrap(self, async_client: AsyncLithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - - async_client.webhooks.unwrap(payload, headers, secret=secret) - - @time_machine.travel(fake_now) - def test_verify_signature(self, async_client: Lithic) -> None: - payload = self.payload - headers = self.headers - secret = self.secret - signature = self.signature - verify = async_client.webhooks.verify_signature - - assert verify(payload=payload, headers=headers, secret=secret) is None - - with pytest.raises(ValueError, match="Webhook timestamp is too old"): - with time_machine.travel(self.fake_now + timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - with pytest.raises(ValueError, match="Webhook timestamp is too new"): - with time_machine.travel(self.fake_now - timedelta(minutes=6)): - assert verify(payload=payload, headers=headers, secret=secret) is False - - # wrong secret - with pytest.raises(ValueError, match=r"Bad secret"): - verify(payload=payload, headers=headers, secret="invalid secret") - - invalid_signature_message = "None of the given webhook signatures match the expected signature" - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers=headers, - secret=f"whsec_{base64.b64encode(b'foo').decode('utf-8')}", - ) - - # multiple signatures - invalid_signature = base64.b64encode(b"my_sig").decode("utf-8") - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v1,{invalid_signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # different signature version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature}"}, - secret=secret, - ) - - assert ( - verify( - payload=payload, - headers={**headers, "webhook-signature": f"v2,{signature} v1,{signature}"}, - secret=secret, - ) - is None - ) - - # missing version - with pytest.raises(ValueError, match=invalid_signature_message): - verify( - payload=payload, - headers={**headers, "webhook-signature": signature}, - secret=secret, - ) - - # non-string payload - with pytest.raises(ValueError, match=r"Webhook body should be a string"): - verify( - payload=cast(Any, {"payload": payload}), - headers=headers, - secret=secret, - ) From 36c5c4e84ee61ca9b9cae51d95c7e9bb1906eec2 Mon Sep 17 00:00:00 2001 From: stainless-app Date: Thu, 14 Mar 2024 16:07:18 -0400 Subject: [PATCH 17/26] chore: add back removed code --- api.md | 11 + integrations/pagination.py | 32 +++ src/lithic/_client.py | 4 + src/lithic/resources/__init__.py | 3 + src/lithic/resources/cards/cards.py | 232 +++++++++++++++++- src/lithic/resources/disputes.py | 55 ++++- src/lithic/resources/events/events.py | 41 +++- src/lithic/resources/webhooks.py | 207 ++++++++++++++++ src/lithic/types/__init__.py | 3 + .../types/card_get_embed_html_params.py | 41 ++++ src/lithic/types/card_get_embed_url_params.py | 41 ++++ src/lithic/types/event_resend_params.py | 41 ++++ tests/api_resources/test_cards.py | 24 ++ tests/api_resources/test_events.py | 14 ++ tests/api_resources/test_webhooks.py | 211 ++++++++++++++++ 15 files changed, 956 insertions(+), 4 deletions(-) create mode 100755 integrations/pagination.py create mode 100644 src/lithic/resources/webhooks.py create mode 100644 src/lithic/types/card_get_embed_html_params.py create mode 100644 src/lithic/types/card_get_embed_url_params.py create mode 100644 src/lithic/types/event_resend_params.py create mode 100644 tests/api_resources/test_webhooks.py diff --git a/api.md b/api.md index 11bd283d..21359f56 100644 --- a/api.md +++ b/api.md @@ -150,6 +150,8 @@ Methods: - client.cards.renew(card_token, \*\*params) -> Card - client.cards.retrieve_spend_limits(card_token) -> CardSpendLimits - client.cards.search_by_pan(\*\*params) -> Card +- client.cards.get_embed_html(\*args) -> str +- client.cards.get_embed_url(\*args) -> URL ## AggregateBalances @@ -219,6 +221,7 @@ Methods: - client.disputes.initiate_evidence_upload(dispute_token, \*\*params) -> DisputeEvidence - client.disputes.list_evidences(dispute_token, \*\*params) -> SyncCursorPage[DisputeEvidence] - client.disputes.retrieve_evidence(evidence_token, \*, dispute_token) -> DisputeEvidence +- client.disputes.upload_evidence(\*args) -> None # Events @@ -233,6 +236,7 @@ Methods: - client.events.retrieve(event_token) -> Event - client.events.list(\*\*params) -> SyncCursorPage[Event] - client.events.list_attempts(event_token, \*\*params) -> SyncCursorPage[MessageAttempt] +- client.events.resend(\*args) -> None ## Subscriptions @@ -352,6 +356,13 @@ Methods: - client.responder_endpoints.delete(\*\*params) -> None - client.responder_endpoints.check_status(\*\*params) -> ResponderEndpointStatus +# Webhooks + +Methods: + +- client.webhooks.unwrap(\*args) -> object +- client.webhooks.verify_signature(\*args) -> None + # ExternalBankAccounts Types: diff --git a/integrations/pagination.py b/integrations/pagination.py new file mode 100755 index 00000000..e874414f --- /dev/null +++ b/integrations/pagination.py @@ -0,0 +1,32 @@ +#!/usr/bin/env -S rye run python integrations/pagination.py + +from __future__ import annotations + +import json + +from lithic import Lithic + +client = Lithic(environment="sandbox") + + +def main() -> None: + page = client.transactions.list() + assert len(page.data) > 0, "No transactions found" + + if not page.has_more or not page.has_next_page(): + raise RuntimeError(f"Expected multiple pages to be present, only got {len(page.data)} items") + + tokens: dict[str, int] = {} + + for transaction in page: + tokens[transaction.token] = tokens.get(transaction.token, 0) + 1 + + duplicates = {token: count for token, count in tokens.items() if count > 1} + if duplicates: + print(json.dumps(duplicates, indent=2)) # noqa: T201 + raise RuntimeError(f"Found {len(duplicates)} duplicate entries!") + + print("Success!") # noqa: T201 + + +main() diff --git a/src/lithic/_client.py b/src/lithic/_client.py index dd58984a..3c0fda1a 100644 --- a/src/lithic/_client.py +++ b/src/lithic/_client.py @@ -76,6 +76,7 @@ class Lithic(SyncAPIClient): financial_accounts: resources.FinancialAccounts transactions: resources.Transactions responder_endpoints: resources.ResponderEndpoints + webhooks: resources.Webhooks external_bank_accounts: resources.ExternalBankAccounts payments: resources.Payments three_ds: resources.ThreeDS @@ -193,6 +194,7 @@ def __init__( self.financial_accounts = resources.FinancialAccounts(self) self.transactions = resources.Transactions(self) self.responder_endpoints = resources.ResponderEndpoints(self) + self.webhooks = resources.Webhooks(self) self.external_bank_accounts = resources.ExternalBankAccounts(self) self.payments = resources.Payments(self) self.three_ds = resources.ThreeDS(self) @@ -366,6 +368,7 @@ class AsyncLithic(AsyncAPIClient): financial_accounts: resources.AsyncFinancialAccounts transactions: resources.AsyncTransactions responder_endpoints: resources.AsyncResponderEndpoints + webhooks: resources.AsyncWebhooks external_bank_accounts: resources.AsyncExternalBankAccounts payments: resources.AsyncPayments three_ds: resources.AsyncThreeDS @@ -483,6 +486,7 @@ def __init__( self.financial_accounts = resources.AsyncFinancialAccounts(self) self.transactions = resources.AsyncTransactions(self) self.responder_endpoints = resources.AsyncResponderEndpoints(self) + self.webhooks = resources.AsyncWebhooks(self) self.external_bank_accounts = resources.AsyncExternalBankAccounts(self) self.payments = resources.AsyncPayments(self) self.three_ds = resources.AsyncThreeDS(self) diff --git a/src/lithic/resources/__init__.py b/src/lithic/resources/__init__.py index cb865d98..aabd710d 100644 --- a/src/lithic/resources/__init__.py +++ b/src/lithic/resources/__init__.py @@ -64,6 +64,7 @@ ThreeDSWithStreamingResponse, AsyncThreeDSWithStreamingResponse, ) +from .webhooks import Webhooks, AsyncWebhooks from .auth_rules import ( AuthRules, AsyncAuthRules, @@ -254,6 +255,8 @@ "AsyncResponderEndpointsWithRawResponse", "ResponderEndpointsWithStreamingResponse", "AsyncResponderEndpointsWithStreamingResponse", + "Webhooks", + "AsyncWebhooks", "ExternalBankAccounts", "AsyncExternalBankAccounts", "ExternalBankAccountsWithRawResponse", diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index ac1cbf44..c56972fb 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -2,11 +2,16 @@ from __future__ import annotations +import hmac +import json +import base64 +import hashlib from typing import Union -from datetime import datetime +from datetime import datetime, timezone, timedelta from typing_extensions import Literal import httpx +from httpx import URL from ... import _legacy_response from ...types import ( @@ -22,6 +27,7 @@ card_update_params, card_reissue_params, card_provision_params, + card_get_embed_url_params, card_search_by_pan_params, ) from ..._types import ( @@ -34,6 +40,7 @@ ) from ..._utils import ( maybe_transform, + strip_not_given, async_maybe_transform, ) from .balances import ( @@ -50,6 +57,7 @@ from ...pagination import SyncCursorPage, AsyncCursorPage from ..._base_client import ( AsyncPaginator, + _merge_mappings, make_request_options, ) from .aggregate_balances import ( @@ -533,6 +541,117 @@ def embed( cast_to=str, ) + def get_embed_html( + self, + *, + token: str, + css: str | NotGiven = NOT_GIVEN, + expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, + target_origin: str | NotGiven = NOT_GIVEN, + # 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, + ) -> str: + """ + Generates and executes an embed request, returning html you can serve to the + user. + + Be aware that this html contains sensitive data whose presence on your server + could trigger PCI DSS. + + If your company is not certified PCI compliant, we recommend using + `get_embed_url()` instead. You would then pass that returned URL to the + frontend, where you can load it via an iframe. + """ + headers = _merge_mappings({"Accept": "text/html"}, extra_headers or {}) + url = self.get_embed_url( + css=css, + token=token, + expiration=expiration, + target_origin=target_origin, + ) + return self._get( + str(url), + cast_to=str, + options=make_request_options( + extra_headers=headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def get_embed_url( + self, + *, + token: str, + css: str | NotGiven = NOT_GIVEN, + expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, + target_origin: str | NotGiven = NOT_GIVEN, + ) -> URL: + """ + Handling full card PANs and CVV codes requires that you comply with the Payment + Card Industry Data Security Standards (PCI DSS). Some clients choose to reduce + their compliance obligations by leveraging our embedded card UI solution + documented below. + + In this setup, PANs and CVV codes are presented to the end-user via a card UI + that we provide, optionally styled in the customer's branding using a specified + css stylesheet. A user's browser makes the request directly to api.lithic.com, + so card PANs and CVVs never touch the API customer's servers while full card + data is displayed to their end-users. The response contains an HTML document. + This means that the url for the request can be inserted straight into the `src` + attribute of an iframe. + + ```html + + ``` + + You should compute the request payload on the server side. You can render it (or + the whole iframe) on the server or make an ajax call from your front end code, + but **do not ever embed your API key into front end code, as doing so introduces + a serious security vulnerability**. + """ + # Default expiration of 1 minute from now. + if isinstance(expiration, NotGiven): + expiration = datetime.now(timezone.utc) + timedelta(minutes=1) + + query = maybe_transform( + strip_not_given( + { + "css": css, + "token": token, + "expiration": expiration, + "target_origin": target_origin, + } + ), + card_get_embed_url_params.CardGetEmbedURLParams, + ) + serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) + params = { + "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), + "hmac": base64.b64encode( + hmac.new( + key=bytes(self._client.api_key, "utf-8"), + msg=bytes(serialized, "utf-8"), + digestmod=hashlib.sha256, + ).digest() + ).decode("utf-8"), + } + + # Copied nearly directly from httpx.BaseClient._merge_url + base_url = self._client.base_url + raw_path = base_url.raw_path + URL("embed/card").raw_path + return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) + def provision( self, card_token: str, @@ -1286,6 +1405,117 @@ async def embed( cast_to=str, ) + async def get_embed_html( + self, + *, + token: str, + css: str | NotGiven = NOT_GIVEN, + expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, + target_origin: str | NotGiven = NOT_GIVEN, + # 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, + ) -> str: + """ + Generates and executes an embed request, returning html you can serve to the + user. + + Be aware that this html contains sensitive data whose presence on your server + could trigger PCI DSS. + + If your company is not certified PCI compliant, we recommend using + `get_embed_url()` instead. You would then pass that returned URL to the + frontend, where you can load it via an iframe. + """ + headers = _merge_mappings({"Accept": "text/html"}, extra_headers or {}) + url = self.get_embed_url( + css=css, + token=token, + expiration=expiration, + target_origin=target_origin, + ) + return await self._get( + str(url), + cast_to=str, + options=make_request_options( + extra_headers=headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ), + ) + + def get_embed_url( + self, + *, + token: str, + css: str | NotGiven = NOT_GIVEN, + expiration: Union[str, datetime] | NotGiven = NOT_GIVEN, + target_origin: str | NotGiven = NOT_GIVEN, + ) -> URL: + """ + Handling full card PANs and CVV codes requires that you comply with the Payment + Card Industry Data Security Standards (PCI DSS). Some clients choose to reduce + their compliance obligations by leveraging our embedded card UI solution + documented below. + + In this setup, PANs and CVV codes are presented to the end-user via a card UI + that we provide, optionally styled in the customer's branding using a specified + css stylesheet. A user's browser makes the request directly to api.lithic.com, + so card PANs and CVVs never touch the API customer's servers while full card + data is displayed to their end-users. The response contains an HTML document. + This means that the url for the request can be inserted straight into the `src` + attribute of an iframe. + + ```html + + ``` + + You should compute the request payload on the server side. You can render it (or + the whole iframe) on the server or make an ajax call from your front end code, + but **do not ever embed your API key into front end code, as doing so introduces + a serious security vulnerability**. + """ + # Default expiration of 1 minute from now. + if isinstance(expiration, NotGiven): + expiration = datetime.now(timezone.utc) + timedelta(minutes=1) + + query = maybe_transform( + strip_not_given( + { + "css": css, + "token": token, + "expiration": expiration, + "target_origin": target_origin, + } + ), + card_get_embed_url_params.CardGetEmbedURLParams, + ) + serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) + params = { + "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), + "hmac": base64.b64encode( + hmac.new( + key=bytes(self._client.api_key, "utf-8"), + msg=bytes(serialized, "utf-8"), + digestmod=hashlib.sha256, + ).digest() + ).decode("utf-8"), + } + + # Copied nearly directly from httpx.BaseClient._merge_url + base_url = self._client.base_url + raw_path = base_url.raw_path + URL("embed/card").raw_path + return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) + async def provision( self, card_token: str, diff --git a/src/lithic/resources/disputes.py b/src/lithic/resources/disputes.py index d2105ccc..ef613e18 100644 --- a/src/lithic/resources/disputes.py +++ b/src/lithic/resources/disputes.py @@ -18,7 +18,16 @@ dispute_list_evidences_params, dispute_initiate_evidence_upload_params, ) -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import ( + NOT_GIVEN, + Body, + Omit, + Query, + Headers, + NoneType, + NotGiven, + FileTypes, +) from .._utils import ( maybe_transform, async_maybe_transform, @@ -517,6 +526,28 @@ def retrieve_evidence( cast_to=DisputeEvidence, ) + def upload_evidence( + self, + dispute_token: str, + file: FileTypes, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + ) -> None: + """ + Initiates the Dispute Evidence Upload, then uploads the file to the returned + `upload_url`. + """ + payload = self._client.disputes.initiate_evidence_upload( + dispute_token, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body + ) + if not payload.upload_url: + raise ValueError("Missing 'upload_url' from response payload") + files = {"file": file} + options = make_request_options(extra_headers={"Authorization": Omit()}) + self._put(payload.upload_url, cast_to=NoneType, body=None, files=files, options=options) + class AsyncDisputes(AsyncAPIResource): @cached_property @@ -1000,6 +1031,28 @@ async def retrieve_evidence( cast_to=DisputeEvidence, ) + async def upload_evidence( + self, + dispute_token: str, + file: FileTypes, + *, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + ) -> None: + """ + Initiates the Dispute Evidence Upload, then uploads the file to the returned + `upload_url`. + """ + payload = await self._client.disputes.initiate_evidence_upload( + dispute_token, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body + ) + if not payload.upload_url: + raise ValueError("Missing 'upload_url' from response payload") + files = {"file": file} + options = make_request_options(extra_headers={"Authorization": Omit()}) + await self._put(payload.upload_url, cast_to=NoneType, body=None, files=files, options=options) + class DisputesWithRawResponse: def __init__(self, disputes: Disputes) -> None: diff --git a/src/lithic/resources/events/events.py b/src/lithic/resources/events/events.py index a9d22cdb..76bb29e4 100644 --- a/src/lithic/resources/events/events.py +++ b/src/lithic/resources/events/events.py @@ -9,8 +9,13 @@ import httpx from ... import _legacy_response -from ...types import Event, MessageAttempt, event_list_params, event_list_attempts_params -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ...types import ( + Event, + MessageAttempt, + event_list_params, + event_list_attempts_params, +) +from ..._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -239,6 +244,22 @@ def list_attempts( model=MessageAttempt, ) + def resend( + self, + event_token: str, + *, + event_subscription_token: str, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + ) -> None: + """Resend an event to an event subscription.""" + self._post( + f"/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body), + cast_to=NoneType, + ) + class AsyncEvents(AsyncAPIResource): @cached_property @@ -447,6 +468,22 @@ def list_attempts( model=MessageAttempt, ) + async def resend( + self, + event_token: str, + *, + event_subscription_token: str, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + ) -> None: + """Resend an event to an event subscription.""" + await self._post( + f"/events/{event_token}/event_subscriptions/{event_subscription_token}/resend", + options=make_request_options(extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body), + cast_to=NoneType, + ) + class EventsWithRawResponse: def __init__(self, events: Events) -> None: diff --git a/src/lithic/resources/webhooks.py b/src/lithic/resources/webhooks.py new file mode 100644 index 00000000..8b5094f8 --- /dev/null +++ b/src/lithic/resources/webhooks.py @@ -0,0 +1,207 @@ +# File generated from our OpenAPI spec by Stainless. + +from __future__ import annotations + +import hmac +import json +import math +import base64 +import hashlib +from datetime import datetime, timezone, timedelta + +from .._types import ( + HeadersLike, +) +from .._utils import ( + removeprefix, + get_required_header, +) +from .._resource import SyncAPIResource, AsyncAPIResource + +__all__ = ["Webhooks", "AsyncWebhooks"] + + +class Webhooks(SyncAPIResource): + def unwrap( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + ) -> object: + """Validates that the given payload was sent by Lithic and parses the payload.""" + self.verify_signature(payload=payload, headers=headers, secret=secret) + return json.loads(payload) + + def verify_signature( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + ) -> None: + """Validates whether or not the webhook payload was sent by Lithic. + + An error will be raised if the webhook payload was not sent by Lithic. + """ + if secret is None: + secret = self._client.webhook_secret + + if secret is None: + raise ValueError( + "The webhook secret must either be set using the env var, LITHIC_WEBHOOK_SECRET, on the client class, Lithic(webhook_secret='123'), or passed to this function" + ) + + try: + whsecret = base64.b64decode(removeprefix(secret, "whsec_")) + except Exception as err: + raise ValueError("Bad secret") from err + + msg_id = get_required_header(headers, "webhook-id") + msg_timestamp = get_required_header(headers, "webhook-timestamp") + + # validate the timestamp + webhook_tolerance = timedelta(minutes=5) + now = datetime.now(tz=timezone.utc) + + try: + timestamp = datetime.fromtimestamp(float(msg_timestamp), tz=timezone.utc) + except Exception as err: + raise ValueError("Invalid signature headers. Could not convert to timestamp") from err + + # too old + if timestamp < (now - webhook_tolerance): + raise ValueError("Webhook timestamp is too old") + + # too new + if timestamp > (now + webhook_tolerance): + raise ValueError("Webhook timestamp is too new") + + # create the signature + body = payload.decode("utf-8") if isinstance(payload, bytes) else payload + if not isinstance(body, str): # pyright: ignore[reportUnnecessaryIsInstance] + raise ValueError( + "Webhook body should be a string of JSON (or bytes which can be decoded to a utf-8 string), not a parsed dictionary." + ) + + timestamp_str = str(math.floor(timestamp.replace(tzinfo=timezone.utc).timestamp())) + + to_sign = f"{msg_id}.{timestamp_str}.{body}".encode() + expected_signature = hmac.new(whsecret, to_sign, hashlib.sha256).digest() + + msg_signature = get_required_header(headers, "webhook-signature") + + # Signature header can contain multiple signatures delimited by spaces + passed_sigs = msg_signature.split(" ") + + for versioned_sig in passed_sigs: + values = versioned_sig.split(",") + if len(values) != 2: + # signature is not formatted like {version},{signature} + continue + + (version, signature) = values + + # Only verify prefix v1 + if version != "v1": + continue + + sig_bytes = base64.b64decode(signature) + if hmac.compare_digest(expected_signature, sig_bytes): + # valid! + return None + + raise ValueError("None of the given webhook signatures match the expected signature") + + +class AsyncWebhooks(AsyncAPIResource): + def unwrap( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + ) -> object: + """Validates that the given payload was sent by Lithic and parses the payload.""" + self.verify_signature(payload=payload, headers=headers, secret=secret) + return json.loads(payload) + + def verify_signature( + self, + payload: str | bytes, + headers: HeadersLike, + *, + secret: str | None = None, + ) -> None: + """Validates whether or not the webhook payload was sent by Lithic. + + An error will be raised if the webhook payload was not sent by Lithic. + """ + if secret is None: + secret = self._client.webhook_secret + + if secret is None: + raise ValueError( + "The webhook secret must either be set using the env var, LITHIC_WEBHOOK_SECRET, on the client class, Lithic(webhook_secret='123'), or passed to this function" + ) + + try: + whsecret = base64.b64decode(removeprefix(secret, "whsec_")) + except Exception as err: + raise ValueError("Bad secret") from err + + msg_id = get_required_header(headers, "webhook-id") + msg_timestamp = get_required_header(headers, "webhook-timestamp") + + # validate the timestamp + webhook_tolerance = timedelta(minutes=5) + now = datetime.now(tz=timezone.utc) + + try: + timestamp = datetime.fromtimestamp(float(msg_timestamp), tz=timezone.utc) + except Exception as err: + raise ValueError("Invalid signature headers. Could not convert to timestamp") from err + + # too old + if timestamp < (now - webhook_tolerance): + raise ValueError("Webhook timestamp is too old") + + # too new + if timestamp > (now + webhook_tolerance): + raise ValueError("Webhook timestamp is too new") + + # create the signature + body = payload.decode("utf-8") if isinstance(payload, bytes) else payload + if not isinstance(body, str): # pyright: ignore[reportUnnecessaryIsInstance] + raise ValueError( + "Webhook body should be a string of JSON (or bytes which can be decoded to a utf-8 string), not a parsed dictionary." + ) + + timestamp_str = str(math.floor(timestamp.replace(tzinfo=timezone.utc).timestamp())) + + to_sign = f"{msg_id}.{timestamp_str}.{body}".encode() + expected_signature = hmac.new(whsecret, to_sign, hashlib.sha256).digest() + + msg_signature = get_required_header(headers, "webhook-signature") + + # Signature header can contain multiple signatures delimited by spaces + passed_sigs = msg_signature.split(" ") + + for versioned_sig in passed_sigs: + values = versioned_sig.split(",") + if len(values) != 2: + # signature is not formatted like {version},{signature} + continue + + (version, signature) = values + + # Only verify prefix v1 + if version != "v1": + continue + + sig_bytes = base64.b64decode(signature) + if hmac.compare_digest(expected_signature, sig_bytes): + # valid! + return None + + raise ValueError("None of the given webhook signatures match the expected signature") diff --git a/src/lithic/types/__init__.py b/src/lithic/types/__init__.py index fce1f10f..bc1b39f6 100644 --- a/src/lithic/types/__init__.py +++ b/src/lithic/types/__init__.py @@ -38,6 +38,7 @@ from .card_embed_response import CardEmbedResponse as CardEmbedResponse from .card_reissue_params import CardReissueParams as CardReissueParams from .dispute_list_params import DisputeListParams as DisputeListParams +from .event_resend_params import EventResendParams as EventResendParams from .payment_list_params import PaymentListParams as PaymentListParams from .tokenization_secret import TokenizationSecret as TokenizationSecret from .verification_method import VerificationMethod as VerificationMethod @@ -62,9 +63,11 @@ from .card_program_list_params import CardProgramListParams as CardProgramListParams from .tokenization_list_params import TokenizationListParams as TokenizationListParams from .auth_rule_remove_response import AuthRuleRemoveResponse as AuthRuleRemoveResponse +from .card_get_embed_url_params import CardGetEmbedURLParams as CardGetEmbedURLParams from .card_search_by_pan_params import CardSearchByPanParams as CardSearchByPanParams from .responder_endpoint_status import ResponderEndpointStatus as ResponderEndpointStatus from .account_holder_list_params import AccountHolderListParams as AccountHolderListParams +from .card_get_embed_html_params import CardGetEmbedHTMLParams as CardGetEmbedHTMLParams from .event_list_attempts_params import EventListAttemptsParams as EventListAttemptsParams from .settlement_summary_details import SettlementSummaryDetails as SettlementSummaryDetails from .auth_rule_retrieve_response import AuthRuleRetrieveResponse as AuthRuleRetrieveResponse diff --git a/src/lithic/types/card_get_embed_html_params.py b/src/lithic/types/card_get_embed_html_params.py new file mode 100644 index 00000000..86bb86d3 --- /dev/null +++ b/src/lithic/types/card_get_embed_html_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["CardGetEmbedHTMLParams"] + + +class CardGetEmbedHTMLParams(TypedDict, total=False): + token: Required[str] + """Globally unique identifier for the card to be displayed.""" + + css: str + """ + A publicly available URI, so the white-labeled card element can be styled with + the client's branding. + """ + + expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """An RFC 3339 timestamp for when the request should expire. UTC time zone. + + If no timezone is specified, UTC will be used. If payload does not contain an + expiration, the request will never expire. + + Using an `expiration` reduces the risk of a + [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying + the `expiration`, in the event that a malicious user gets a copy of your request + in transit, they will be able to obtain the response data indefinitely. + """ + + target_origin: str + """Required if you want to post the element clicked to the parent iframe. + + If you supply this param, you can also capture click events in the parent iframe + by adding an event listener. + """ diff --git a/src/lithic/types/card_get_embed_url_params.py b/src/lithic/types/card_get_embed_url_params.py new file mode 100644 index 00000000..df77c553 --- /dev/null +++ b/src/lithic/types/card_get_embed_url_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["CardGetEmbedURLParams"] + + +class CardGetEmbedURLParams(TypedDict, total=False): + token: Required[str] + """Globally unique identifier for the card to be displayed.""" + + css: str + """ + A publicly available URI, so the white-labeled card element can be styled with + the client's branding. + """ + + expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """An RFC 3339 timestamp for when the request should expire. UTC time zone. + + If no timezone is specified, UTC will be used. If payload does not contain an + expiration, the request will never expire. + + Using an `expiration` reduces the risk of a + [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying + the `expiration`, in the event that a malicious user gets a copy of your request + in transit, they will be able to obtain the response data indefinitely. + """ + + target_origin: str + """Required if you want to post the element clicked to the parent iframe. + + If you supply this param, you can also capture click events in the parent iframe + by adding an event listener. + """ diff --git a/src/lithic/types/event_resend_params.py b/src/lithic/types/event_resend_params.py new file mode 100644 index 00000000..e381da50 --- /dev/null +++ b/src/lithic/types/event_resend_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. + +from __future__ import annotations + +from typing import Union +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["EventResendParams"] + + +class EventResendParams(TypedDict, total=False): + token: Required[str] + """Globally unique identifier for the card to be displayed.""" + + css: str + """ + A publicly available URI, so the white-labeled card element can be styled with + the client's branding. + """ + + expiration: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """An RFC 3339 timestamp for when the request should expire. UTC time zone. + + If no timezone is specified, UTC will be used. If payload does not contain an + expiration, the request will never expire. + + Using an `expiration` reduces the risk of a + [replay attack](https://en.wikipedia.org/wiki/Replay_attack). Without supplying + the `expiration`, in the event that a malicious user gets a copy of your request + in transit, they will be able to obtain the response data indefinitely. + """ + + target_origin: str + """Required if you want to post the element clicked to the parent iframe. + + If you supply this param, you can also capture click events in the parent iframe + by adding an event listener. + """ diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index fa03d2ec..a01331e0 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -250,6 +250,18 @@ def test_streaming_response_embed(self, client: Lithic) -> None: assert cast(Any, response.is_closed) is True + def test_get_embed_html(self, client: Lithic) -> None: + html = client.cards.get_embed_html(token="foo") + assert "html" in html + + def test_get_embed_url(self, client: Lithic) -> None: + url = client.cards.get_embed_url(token="foo") + params = set( # pyright: ignore[reportUnknownVariableType] + url.params.keys() # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] + ) + assert "hmac" in params + assert "embed_request" in params + @parametrize def test_method_provision(self, client: Lithic) -> None: card = client.cards.provision( @@ -759,6 +771,18 @@ async def test_streaming_response_embed(self, async_client: AsyncLithic) -> None assert cast(Any, response.is_closed) is True + async def test_get_embed_html(self, async_client: AsyncLithic) -> None: + html = await async_client.cards.get_embed_html(token="foo") + assert "html" in html + + def test_get_embed_url(self, async_client: Lithic) -> None: + url = async_client.cards.get_embed_url(token="foo") + params = set( # pyright: ignore[reportUnknownVariableType] + url.params.keys() # pyright: ignore[reportUnknownMemberType,reportUnknownArgumentType] + ) + assert "hmac" in params + assert "embed_request" in params + @parametrize async def test_method_provision(self, async_client: AsyncLithic) -> None: card = await async_client.cards.provision( diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py index 267caa8c..893dc631 100644 --- a/tests/api_resources/test_events.py +++ b/tests/api_resources/test_events.py @@ -146,6 +146,13 @@ def test_path_params_list_attempts(self, client: Lithic) -> None: "", ) + @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + def test_method_resend(self, client: Lithic) -> None: + client.events.resend( + "string", + event_subscription_token="string", + ) + class TestAsyncEvents: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -276,3 +283,10 @@ async def test_path_params_list_attempts(self, async_client: AsyncLithic) -> Non await async_client.events.with_raw_response.list_attempts( "", ) + + @pytest.mark.skip(reason="Prism Mock server doesnt want Accept header, but server requires it.") + async def test_method_resend(self, async_client: AsyncLithic) -> None: + await async_client.events.resend( + "string", + event_subscription_token="string", + ) diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py new file mode 100644 index 00000000..c3fc25b5 --- /dev/null +++ b/tests/api_resources/test_webhooks.py @@ -0,0 +1,211 @@ +# File generated from our OpenAPI spec by Stainless. + +from __future__ import annotations + +import os +import base64 +from typing import Any, cast +from datetime import datetime, timezone, timedelta + +import pytest +import time_machine + +from lithic import Lithic, AsyncLithic + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestWebhooks: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + timestamp = "1676312382" + fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) + + payload = """{"card_token":"sit Lorem ipsum, accusantium repellendus possimus","created_at":"elit. placeat libero architecto molestias, sit","account_token":"elit.","issuer_decision":"magnam, libero esse Lorem ipsum magnam, magnam,","tokenization_attempt_id":"illum dolor repellendus libero esse accusantium","wallet_decisioning_info":{"device_score":"placeat architecto"},"digital_wallet_token_metadata":{"status":"reprehenderit dolor","token_requestor_id":"possimus","payment_account_info":{"account_holder_data":{"phone_number":"libero","email_address":"nobis molestias, veniam culpa! quas elit. quas libero esse architecto placeat"},"pan_unique_reference":"adipisicing odit magnam, odit"}}}""" + signature = "Dwa0AHInLL3XFo2sxcHamOQDrJNi7F654S3L6skMAOI=" + headers = { + "webhook-id": "msg_2Lh9KRb0pzN4LePd3XiA4v12Axj", + "webhook-timestamp": timestamp, + "webhook-signature": f"v1,{signature}", + } + secret = "whsec_zlFsbBZ8Xcodlpcu6NDTdSzZRLSdhkst" + + @time_machine.travel(fake_now) + def test_unwrap(self, client: Lithic) -> None: + payload = self.payload + headers = self.headers + secret = self.secret + + client.webhooks.unwrap(payload, headers, secret=secret) + + @time_machine.travel(fake_now) + def test_verify_signature(self, client: Lithic) -> None: + payload = self.payload + headers = self.headers + secret = self.secret + signature = self.signature + verify = client.webhooks.verify_signature + + assert verify(payload=payload, headers=headers, secret=secret) is None + + with pytest.raises(ValueError, match="Webhook timestamp is too old"): + with time_machine.travel(self.fake_now + timedelta(minutes=6)): + assert verify(payload=payload, headers=headers, secret=secret) is False + + with pytest.raises(ValueError, match="Webhook timestamp is too new"): + with time_machine.travel(self.fake_now - timedelta(minutes=6)): + assert verify(payload=payload, headers=headers, secret=secret) is False + + # wrong secret + with pytest.raises(ValueError, match=r"Bad secret"): + verify(payload=payload, headers=headers, secret="invalid secret") + + invalid_signature_message = "None of the given webhook signatures match the expected signature" + with pytest.raises(ValueError, match=invalid_signature_message): + verify( + payload=payload, + headers=headers, + secret=f"whsec_{base64.b64encode(b'foo').decode('utf-8')}", + ) + + # multiple signatures + invalid_signature = base64.b64encode(b"my_sig").decode("utf-8") + assert ( + verify( + payload=payload, + headers={**headers, "webhook-signature": f"v1,{invalid_signature} v1,{signature}"}, + secret=secret, + ) + is None + ) + + # different signature version + with pytest.raises(ValueError, match=invalid_signature_message): + verify( + payload=payload, + headers={**headers, "webhook-signature": f"v2,{signature}"}, + secret=secret, + ) + + assert ( + verify( + payload=payload, + headers={**headers, "webhook-signature": f"v2,{signature} v1,{signature}"}, + secret=secret, + ) + is None + ) + + # missing version + with pytest.raises(ValueError, match=invalid_signature_message): + verify( + payload=payload, + headers={**headers, "webhook-signature": signature}, + secret=secret, + ) + + # non-string payload + with pytest.raises(ValueError, match=r"Webhook body should be a string"): + verify( + payload=cast(Any, {"payload": payload}), + headers=headers, + secret=secret, + ) + + +class TestAsyncWebhooks: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + timestamp = "1676312382" + fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) + + payload = """{"card_token":"sit Lorem ipsum, accusantium repellendus possimus","created_at":"elit. placeat libero architecto molestias, sit","account_token":"elit.","issuer_decision":"magnam, libero esse Lorem ipsum magnam, magnam,","tokenization_attempt_id":"illum dolor repellendus libero esse accusantium","wallet_decisioning_info":{"device_score":"placeat architecto"},"digital_wallet_token_metadata":{"status":"reprehenderit dolor","token_requestor_id":"possimus","payment_account_info":{"account_holder_data":{"phone_number":"libero","email_address":"nobis molestias, veniam culpa! quas elit. quas libero esse architecto placeat"},"pan_unique_reference":"adipisicing odit magnam, odit"}}}""" + signature = "Dwa0AHInLL3XFo2sxcHamOQDrJNi7F654S3L6skMAOI=" + headers = { + "webhook-id": "msg_2Lh9KRb0pzN4LePd3XiA4v12Axj", + "webhook-timestamp": timestamp, + "webhook-signature": f"v1,{signature}", + } + secret = "whsec_zlFsbBZ8Xcodlpcu6NDTdSzZRLSdhkst" + + @time_machine.travel(fake_now) + def test_unwrap(self, async_client: AsyncLithic) -> None: + payload = self.payload + headers = self.headers + secret = self.secret + + async_client.webhooks.unwrap(payload, headers, secret=secret) + + @time_machine.travel(fake_now) + def test_verify_signature(self, async_client: Lithic) -> None: + payload = self.payload + headers = self.headers + secret = self.secret + signature = self.signature + verify = async_client.webhooks.verify_signature + + assert verify(payload=payload, headers=headers, secret=secret) is None + + with pytest.raises(ValueError, match="Webhook timestamp is too old"): + with time_machine.travel(self.fake_now + timedelta(minutes=6)): + assert verify(payload=payload, headers=headers, secret=secret) is False + + with pytest.raises(ValueError, match="Webhook timestamp is too new"): + with time_machine.travel(self.fake_now - timedelta(minutes=6)): + assert verify(payload=payload, headers=headers, secret=secret) is False + + # wrong secret + with pytest.raises(ValueError, match=r"Bad secret"): + verify(payload=payload, headers=headers, secret="invalid secret") + + invalid_signature_message = "None of the given webhook signatures match the expected signature" + with pytest.raises(ValueError, match=invalid_signature_message): + verify( + payload=payload, + headers=headers, + secret=f"whsec_{base64.b64encode(b'foo').decode('utf-8')}", + ) + + # multiple signatures + invalid_signature = base64.b64encode(b"my_sig").decode("utf-8") + assert ( + verify( + payload=payload, + headers={**headers, "webhook-signature": f"v1,{invalid_signature} v1,{signature}"}, + secret=secret, + ) + is None + ) + + # different signature version + with pytest.raises(ValueError, match=invalid_signature_message): + verify( + payload=payload, + headers={**headers, "webhook-signature": f"v2,{signature}"}, + secret=secret, + ) + + assert ( + verify( + payload=payload, + headers={**headers, "webhook-signature": f"v2,{signature} v1,{signature}"}, + secret=secret, + ) + is None + ) + + # missing version + with pytest.raises(ValueError, match=invalid_signature_message): + verify( + payload=payload, + headers={**headers, "webhook-signature": signature}, + secret=secret, + ) + + # non-string payload + with pytest.raises(ValueError, match=r"Webhook body should be a string"): + verify( + payload=cast(Any, {"payload": payload}), + headers=headers, + secret=secret, + ) From 16f35fad0bc1f250028991635f39ee34c29c450a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 20:52:05 +0000 Subject: [PATCH 18/26] perf: cache TypeAdapters (#389) --- src/lithic/_models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lithic/_models.py b/src/lithic/_models.py index 88afa408..16697353 100644 --- a/src/lithic/_models.py +++ b/src/lithic/_models.py @@ -3,6 +3,7 @@ import inspect from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast from datetime import date, datetime +from functools import lru_cache from typing_extensions import ( Unpack, Literal, @@ -533,7 +534,12 @@ class GenericModel(BaseGenericModel, BaseModel): if PYDANTIC_V2: - from pydantic import TypeAdapter + if TYPE_CHECKING: + from pydantic import TypeAdapter + else: + from pydantic import TypeAdapter as _TypeAdapter + + TypeAdapter = lru_cache(_TypeAdapter) def _validate_non_model_type(*, type_: type[_T], value: object) -> _T: return TypeAdapter(type_).validate_python(value) From 2fc74c260e3166ed92c7613f6952604b253d6607 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:27:02 +0000 Subject: [PATCH 19/26] docs: fix typo in CONTRIBUTING.md (#390) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index deb22147..f94e9815 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,7 +86,7 @@ Most tests require you to [set up a mock server](https://github.com/stoplightio/ ```bash # you will need npm installed -npx prism path/to/your/openapi.yml +npx prism mock path/to/your/openapi.yml ``` ```bash From cac2a8b1319904b4d07a7864d426284ebd557d63 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:41:52 +0000 Subject: [PATCH 20/26] chore(internal): update generated pragma comment (#391) --- src/lithic/__init__.py | 2 +- src/lithic/_client.py | 2 +- src/lithic/_constants.py | 2 +- src/lithic/_exceptions.py | 2 +- src/lithic/_resource.py | 2 +- src/lithic/_version.py | 2 +- src/lithic/pagination.py | 2 +- src/lithic/resources/__init__.py | 2 +- src/lithic/resources/account_holders.py | 2 +- src/lithic/resources/accounts/__init__.py | 2 +- src/lithic/resources/accounts/accounts.py | 2 +- src/lithic/resources/accounts/credit_configurations.py | 2 +- src/lithic/resources/aggregate_balances.py | 2 +- src/lithic/resources/auth_rules.py | 2 +- src/lithic/resources/auth_stream_enrollment.py | 2 +- src/lithic/resources/balances.py | 2 +- src/lithic/resources/card_product.py | 2 +- src/lithic/resources/card_programs.py | 2 +- src/lithic/resources/cards/__init__.py | 2 +- src/lithic/resources/cards/aggregate_balances.py | 2 +- src/lithic/resources/cards/balances.py | 2 +- src/lithic/resources/cards/cards.py | 2 +- src/lithic/resources/cards/financial_transactions.py | 2 +- src/lithic/resources/digital_card_art.py | 2 +- src/lithic/resources/disputes.py | 2 +- src/lithic/resources/events/__init__.py | 2 +- src/lithic/resources/events/events.py | 2 +- src/lithic/resources/events/subscriptions.py | 2 +- src/lithic/resources/external_bank_accounts/__init__.py | 2 +- .../resources/external_bank_accounts/external_bank_accounts.py | 2 +- src/lithic/resources/external_bank_accounts/micro_deposits.py | 2 +- src/lithic/resources/financial_accounts/__init__.py | 2 +- src/lithic/resources/financial_accounts/balances.py | 2 +- src/lithic/resources/financial_accounts/financial_accounts.py | 2 +- .../resources/financial_accounts/financial_transactions.py | 2 +- src/lithic/resources/financial_accounts/statements/__init__.py | 2 +- .../resources/financial_accounts/statements/line_items.py | 2 +- .../resources/financial_accounts/statements/statements.py | 2 +- src/lithic/resources/payments.py | 2 +- src/lithic/resources/reports/__init__.py | 2 +- src/lithic/resources/reports/reports.py | 2 +- src/lithic/resources/reports/settlement.py | 2 +- src/lithic/resources/responder_endpoints.py | 2 +- src/lithic/resources/three_ds/__init__.py | 2 +- src/lithic/resources/three_ds/authentication.py | 2 +- src/lithic/resources/three_ds/decisioning.py | 2 +- src/lithic/resources/three_ds/three_ds.py | 2 +- src/lithic/resources/tokenization_decisioning.py | 2 +- src/lithic/resources/tokenizations.py | 2 +- src/lithic/resources/transactions.py | 2 +- src/lithic/types/__init__.py | 2 +- src/lithic/types/account.py | 2 +- src/lithic/types/account_holder.py | 2 +- src/lithic/types/account_holder_create_params.py | 2 +- src/lithic/types/account_holder_create_response.py | 2 +- src/lithic/types/account_holder_document.py | 2 +- src/lithic/types/account_holder_list_documents_response.py | 2 +- src/lithic/types/account_holder_list_params.py | 2 +- src/lithic/types/account_holder_resubmit_params.py | 2 +- src/lithic/types/account_holder_update_params.py | 2 +- src/lithic/types/account_holder_update_response.py | 2 +- src/lithic/types/account_holder_upload_document_params.py | 2 +- src/lithic/types/account_list_params.py | 2 +- src/lithic/types/account_spend_limits.py | 2 +- src/lithic/types/account_update_params.py | 2 +- src/lithic/types/accounts/__init__.py | 2 +- src/lithic/types/accounts/credit_configuration_update_params.py | 2 +- src/lithic/types/aggregate_balance.py | 2 +- src/lithic/types/aggregate_balance_list_params.py | 2 +- src/lithic/types/api_status.py | 2 +- src/lithic/types/auth_rule.py | 2 +- src/lithic/types/auth_rule_apply_params.py | 2 +- src/lithic/types/auth_rule_create_params.py | 2 +- src/lithic/types/auth_rule_list_params.py | 2 +- src/lithic/types/auth_rule_remove_params.py | 2 +- src/lithic/types/auth_rule_remove_response.py | 2 +- src/lithic/types/auth_rule_retrieve_response.py | 2 +- src/lithic/types/auth_rule_update_params.py | 2 +- src/lithic/types/auth_stream_secret.py | 2 +- src/lithic/types/balance.py | 2 +- src/lithic/types/balance_list_params.py | 2 +- src/lithic/types/business_account.py | 2 +- src/lithic/types/card.py | 2 +- src/lithic/types/card_create_params.py | 2 +- src/lithic/types/card_embed_params.py | 2 +- src/lithic/types/card_embed_response.py | 2 +- src/lithic/types/card_list_params.py | 2 +- src/lithic/types/card_product_credit_detail_response.py | 2 +- src/lithic/types/card_program.py | 2 +- src/lithic/types/card_program_list_params.py | 2 +- src/lithic/types/card_provision_params.py | 2 +- src/lithic/types/card_provision_response.py | 2 +- src/lithic/types/card_reissue_params.py | 2 +- src/lithic/types/card_renew_params.py | 2 +- src/lithic/types/card_search_by_pan_params.py | 2 +- src/lithic/types/card_spend_limits.py | 2 +- src/lithic/types/card_update_params.py | 2 +- src/lithic/types/cards/__init__.py | 2 +- src/lithic/types/cards/aggregate_balance_list_params.py | 2 +- src/lithic/types/cards/aggregate_balance_list_response.py | 2 +- src/lithic/types/cards/balance_list_params.py | 2 +- src/lithic/types/cards/financial_transaction_list_params.py | 2 +- src/lithic/types/digital_card_art.py | 2 +- src/lithic/types/digital_card_art_list_params.py | 2 +- src/lithic/types/dispute.py | 2 +- src/lithic/types/dispute_create_params.py | 2 +- src/lithic/types/dispute_evidence.py | 2 +- src/lithic/types/dispute_initiate_evidence_upload_params.py | 2 +- src/lithic/types/dispute_list_evidences_params.py | 2 +- src/lithic/types/dispute_list_params.py | 2 +- src/lithic/types/dispute_update_params.py | 2 +- src/lithic/types/event.py | 2 +- src/lithic/types/event_list_attempts_params.py | 2 +- src/lithic/types/event_list_params.py | 2 +- src/lithic/types/event_subscription.py | 2 +- src/lithic/types/events/__init__.py | 2 +- src/lithic/types/events/subscription_create_params.py | 2 +- src/lithic/types/events/subscription_list_attempts_params.py | 2 +- src/lithic/types/events/subscription_list_params.py | 2 +- src/lithic/types/events/subscription_recover_params.py | 2 +- src/lithic/types/events/subscription_replay_missing_params.py | 2 +- .../types/events/subscription_retrieve_secret_response.py | 2 +- .../types/events/subscription_send_simulated_example_params.py | 2 +- src/lithic/types/events/subscription_update_params.py | 2 +- src/lithic/types/external_bank_account_address.py | 2 +- src/lithic/types/external_bank_account_address_param.py | 2 +- src/lithic/types/external_bank_account_create_params.py | 2 +- src/lithic/types/external_bank_account_create_response.py | 2 +- src/lithic/types/external_bank_account_list_params.py | 2 +- src/lithic/types/external_bank_account_list_response.py | 2 +- src/lithic/types/external_bank_account_retrieve_response.py | 2 +- .../external_bank_account_retry_micro_deposits_response.py | 2 +- src/lithic/types/external_bank_account_update_params.py | 2 +- src/lithic/types/external_bank_account_update_response.py | 2 +- src/lithic/types/external_bank_accounts/__init__.py | 2 +- .../types/external_bank_accounts/micro_deposit_create_params.py | 2 +- .../external_bank_accounts/micro_deposit_create_response.py | 2 +- src/lithic/types/financial_account.py | 2 +- src/lithic/types/financial_account_create_params.py | 2 +- src/lithic/types/financial_account_list_params.py | 2 +- src/lithic/types/financial_account_update_params.py | 2 +- src/lithic/types/financial_accounts/__init__.py | 2 +- src/lithic/types/financial_accounts/balance_list_params.py | 2 +- .../financial_accounts/financial_transaction_list_params.py | 2 +- src/lithic/types/financial_accounts/statement.py | 2 +- src/lithic/types/financial_accounts/statement_list_params.py | 2 +- src/lithic/types/financial_accounts/statements/__init__.py | 2 +- .../financial_accounts/statements/line_item_list_params.py | 2 +- .../financial_accounts/statements/line_item_list_response.py | 2 +- src/lithic/types/financial_transaction.py | 2 +- src/lithic/types/message_attempt.py | 2 +- src/lithic/types/owner_type.py | 2 +- src/lithic/types/payment.py | 2 +- src/lithic/types/payment_create_params.py | 2 +- src/lithic/types/payment_create_response.py | 2 +- src/lithic/types/payment_list_params.py | 2 +- src/lithic/types/payment_retry_response.py | 2 +- src/lithic/types/payment_simulate_release_params.py | 2 +- src/lithic/types/payment_simulate_release_response.py | 2 +- src/lithic/types/payment_simulate_return_params.py | 2 +- src/lithic/types/payment_simulate_return_response.py | 2 +- src/lithic/types/reports/__init__.py | 2 +- src/lithic/types/reports/settlement_list_details_params.py | 2 +- src/lithic/types/responder_endpoint_check_status_params.py | 2 +- src/lithic/types/responder_endpoint_create_params.py | 2 +- src/lithic/types/responder_endpoint_create_response.py | 2 +- src/lithic/types/responder_endpoint_delete_params.py | 2 +- src/lithic/types/responder_endpoint_status.py | 2 +- src/lithic/types/settlement_detail.py | 2 +- src/lithic/types/settlement_report.py | 2 +- src/lithic/types/settlement_summary_details.py | 2 +- src/lithic/types/shared/__init__.py | 2 +- src/lithic/types/shared/address.py | 2 +- src/lithic/types/shared/carrier.py | 2 +- src/lithic/types/shared/shipping_address.py | 2 +- src/lithic/types/shared_params/__init__.py | 2 +- src/lithic/types/shared_params/address.py | 2 +- src/lithic/types/shared_params/carrier.py | 2 +- src/lithic/types/shared_params/shipping_address.py | 2 +- src/lithic/types/spend_limit_duration.py | 2 +- src/lithic/types/three_ds/__init__.py | 2 +- src/lithic/types/three_ds/authentication_retrieve_response.py | 2 +- src/lithic/types/three_ds/authentication_simulate_params.py | 2 +- src/lithic/types/three_ds/authentication_simulate_response.py | 2 +- .../types/three_ds/decisioning_retrieve_secret_response.py | 2 +- src/lithic/types/tokenization.py | 2 +- .../types/tokenization_decisioning_rotate_secret_response.py | 2 +- src/lithic/types/tokenization_list_params.py | 2 +- src/lithic/types/tokenization_retrieve_response.py | 2 +- src/lithic/types/tokenization_secret.py | 2 +- src/lithic/types/tokenization_simulate_params.py | 2 +- src/lithic/types/tokenization_simulate_response.py | 2 +- src/lithic/types/transaction.py | 2 +- src/lithic/types/transaction_list_params.py | 2 +- .../types/transaction_simulate_authorization_advice_params.py | 2 +- .../types/transaction_simulate_authorization_advice_response.py | 2 +- src/lithic/types/transaction_simulate_authorization_params.py | 2 +- src/lithic/types/transaction_simulate_authorization_response.py | 2 +- src/lithic/types/transaction_simulate_clearing_params.py | 2 +- src/lithic/types/transaction_simulate_clearing_response.py | 2 +- .../types/transaction_simulate_credit_authorization_params.py | 2 +- .../types/transaction_simulate_credit_authorization_response.py | 2 +- src/lithic/types/transaction_simulate_return_params.py | 2 +- src/lithic/types/transaction_simulate_return_response.py | 2 +- src/lithic/types/transaction_simulate_return_reversal_params.py | 2 +- .../types/transaction_simulate_return_reversal_response.py | 2 +- src/lithic/types/transaction_simulate_void_params.py | 2 +- src/lithic/types/transaction_simulate_void_response.py | 2 +- src/lithic/types/verification_method.py | 2 +- tests/__init__.py | 2 +- tests/api_resources/__init__.py | 2 +- tests/api_resources/accounts/__init__.py | 2 +- tests/api_resources/accounts/test_credit_configurations.py | 2 +- tests/api_resources/cards/__init__.py | 2 +- tests/api_resources/cards/test_aggregate_balances.py | 2 +- tests/api_resources/cards/test_balances.py | 2 +- tests/api_resources/cards/test_financial_transactions.py | 2 +- tests/api_resources/events/__init__.py | 2 +- tests/api_resources/events/test_subscriptions.py | 2 +- tests/api_resources/external_bank_accounts/__init__.py | 2 +- .../api_resources/external_bank_accounts/test_micro_deposits.py | 2 +- tests/api_resources/financial_accounts/__init__.py | 2 +- tests/api_resources/financial_accounts/statements/__init__.py | 2 +- .../financial_accounts/statements/test_line_items.py | 2 +- tests/api_resources/financial_accounts/test_balances.py | 2 +- .../financial_accounts/test_financial_transactions.py | 2 +- tests/api_resources/financial_accounts/test_statements.py | 2 +- tests/api_resources/reports/__init__.py | 2 +- tests/api_resources/reports/test_settlement.py | 2 +- tests/api_resources/test_account_holders.py | 2 +- tests/api_resources/test_accounts.py | 2 +- tests/api_resources/test_aggregate_balances.py | 2 +- tests/api_resources/test_auth_rules.py | 2 +- tests/api_resources/test_auth_stream_enrollment.py | 2 +- tests/api_resources/test_balances.py | 2 +- tests/api_resources/test_card_product.py | 2 +- tests/api_resources/test_card_programs.py | 2 +- tests/api_resources/test_cards.py | 2 +- tests/api_resources/test_digital_card_art.py | 2 +- tests/api_resources/test_disputes.py | 2 +- tests/api_resources/test_events.py | 2 +- tests/api_resources/test_external_bank_accounts.py | 2 +- tests/api_resources/test_financial_accounts.py | 2 +- tests/api_resources/test_payments.py | 2 +- tests/api_resources/test_responder_endpoints.py | 2 +- tests/api_resources/test_tokenization_decisioning.py | 2 +- tests/api_resources/test_tokenizations.py | 2 +- tests/api_resources/test_top_level.py | 2 +- tests/api_resources/test_transactions.py | 2 +- tests/api_resources/three_ds/__init__.py | 2 +- tests/api_resources/three_ds/test_authentication.py | 2 +- tests/api_resources/three_ds/test_decisioning.py | 2 +- tests/test_client.py | 2 +- 253 files changed, 253 insertions(+), 253 deletions(-) diff --git a/src/lithic/__init__.py b/src/lithic/__init__.py index f9fb6c18..e77db12e 100644 --- a/src/lithic/__init__.py +++ b/src/lithic/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from . import types from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes diff --git a/src/lithic/_client.py b/src/lithic/_client.py index 3c0fda1a..d49a4a91 100644 --- a/src/lithic/_client.py +++ b/src/lithic/_client.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/_constants.py b/src/lithic/_constants.py index bf15141a..1d56f7fd 100644 --- a/src/lithic/_constants.py +++ b/src/lithic/_constants.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import httpx diff --git a/src/lithic/_exceptions.py b/src/lithic/_exceptions.py index a9133f78..0d7465a0 100644 --- a/src/lithic/_exceptions.py +++ b/src/lithic/_exceptions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/_resource.py b/src/lithic/_resource.py index f38e1085..62fc8293 100644 --- a/src/lithic/_resource.py +++ b/src/lithic/_resource.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/_version.py b/src/lithic/_version.py index 4381d2f0..57b356e4 100644 --- a/src/lithic/_version.py +++ b/src/lithic/_version.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "lithic" __version__ = "0.39.0" # x-release-please-version diff --git a/src/lithic/pagination.py b/src/lithic/pagination.py index 31ab30d4..7c525eee 100644 --- a/src/lithic/pagination.py +++ b/src/lithic/pagination.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Any, List, Generic, TypeVar, Optional, cast from typing_extensions import Protocol, override, runtime_checkable diff --git a/src/lithic/resources/__init__.py b/src/lithic/resources/__init__.py index aabd710d..e04c69b6 100644 --- a/src/lithic/resources/__init__.py +++ b/src/lithic/resources/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .cards import ( Cards, diff --git a/src/lithic/resources/account_holders.py b/src/lithic/resources/account_holders.py index d2ba086b..e2d37419 100644 --- a/src/lithic/resources/account_holders.py +++ b/src/lithic/resources/account_holders.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/accounts/__init__.py b/src/lithic/resources/accounts/__init__.py index ab103384..242577bf 100644 --- a/src/lithic/resources/accounts/__init__.py +++ b/src/lithic/resources/accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .accounts import ( Accounts, diff --git a/src/lithic/resources/accounts/accounts.py b/src/lithic/resources/accounts/accounts.py index be098d53..7dac775b 100644 --- a/src/lithic/resources/accounts/accounts.py +++ b/src/lithic/resources/accounts/accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/accounts/credit_configurations.py b/src/lithic/resources/accounts/credit_configurations.py index b339f127..6932bd82 100644 --- a/src/lithic/resources/accounts/credit_configurations.py +++ b/src/lithic/resources/accounts/credit_configurations.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/aggregate_balances.py b/src/lithic/resources/aggregate_balances.py index 502542ca..b757c9d0 100644 --- a/src/lithic/resources/aggregate_balances.py +++ b/src/lithic/resources/aggregate_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/auth_rules.py b/src/lithic/resources/auth_rules.py index 293129b4..ecf23263 100644 --- a/src/lithic/resources/auth_rules.py +++ b/src/lithic/resources/auth_rules.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/auth_stream_enrollment.py b/src/lithic/resources/auth_stream_enrollment.py index 231dcbee..1383fb66 100644 --- a/src/lithic/resources/auth_stream_enrollment.py +++ b/src/lithic/resources/auth_stream_enrollment.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/balances.py b/src/lithic/resources/balances.py index ff18ab1c..a0e852bb 100644 --- a/src/lithic/resources/balances.py +++ b/src/lithic/resources/balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/card_product.py b/src/lithic/resources/card_product.py index 857a808d..814bf5fb 100644 --- a/src/lithic/resources/card_product.py +++ b/src/lithic/resources/card_product.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/card_programs.py b/src/lithic/resources/card_programs.py index 8338431c..37c4c882 100644 --- a/src/lithic/resources/card_programs.py +++ b/src/lithic/resources/card_programs.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/cards/__init__.py b/src/lithic/resources/cards/__init__.py index 9159556e..790baf59 100644 --- a/src/lithic/resources/cards/__init__.py +++ b/src/lithic/resources/cards/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .cards import ( Cards, diff --git a/src/lithic/resources/cards/aggregate_balances.py b/src/lithic/resources/cards/aggregate_balances.py index d89c7f60..5e32be1f 100644 --- a/src/lithic/resources/cards/aggregate_balances.py +++ b/src/lithic/resources/cards/aggregate_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/cards/balances.py b/src/lithic/resources/cards/balances.py index dbafa920..5c17b8c3 100644 --- a/src/lithic/resources/cards/balances.py +++ b/src/lithic/resources/cards/balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index c56972fb..f7dd79c1 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/cards/financial_transactions.py b/src/lithic/resources/cards/financial_transactions.py index 57864af9..99753567 100644 --- a/src/lithic/resources/cards/financial_transactions.py +++ b/src/lithic/resources/cards/financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/digital_card_art.py b/src/lithic/resources/digital_card_art.py index 714026c9..1bef01f2 100644 --- a/src/lithic/resources/digital_card_art.py +++ b/src/lithic/resources/digital_card_art.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/disputes.py b/src/lithic/resources/disputes.py index ef613e18..4a74a9d9 100644 --- a/src/lithic/resources/disputes.py +++ b/src/lithic/resources/disputes.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/events/__init__.py b/src/lithic/resources/events/__init__.py index d788f91f..022e67df 100644 --- a/src/lithic/resources/events/__init__.py +++ b/src/lithic/resources/events/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .events import ( Events, diff --git a/src/lithic/resources/events/events.py b/src/lithic/resources/events/events.py index 76bb29e4..1c927593 100644 --- a/src/lithic/resources/events/events.py +++ b/src/lithic/resources/events/events.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/events/subscriptions.py b/src/lithic/resources/events/subscriptions.py index b5e7d2d9..0bb4c706 100644 --- a/src/lithic/resources/events/subscriptions.py +++ b/src/lithic/resources/events/subscriptions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/external_bank_accounts/__init__.py b/src/lithic/resources/external_bank_accounts/__init__.py index 135f4be6..7e7201a8 100644 --- a/src/lithic/resources/external_bank_accounts/__init__.py +++ b/src/lithic/resources/external_bank_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .micro_deposits import ( MicroDeposits, diff --git a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py index 41e97093..433604d2 100644 --- a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py +++ b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/external_bank_accounts/micro_deposits.py b/src/lithic/resources/external_bank_accounts/micro_deposits.py index 17adb25b..63f18ba6 100644 --- a/src/lithic/resources/external_bank_accounts/micro_deposits.py +++ b/src/lithic/resources/external_bank_accounts/micro_deposits.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/financial_accounts/__init__.py b/src/lithic/resources/financial_accounts/__init__.py index e9c62a23..4aabf330 100644 --- a/src/lithic/resources/financial_accounts/__init__.py +++ b/src/lithic/resources/financial_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .balances import ( Balances, diff --git a/src/lithic/resources/financial_accounts/balances.py b/src/lithic/resources/financial_accounts/balances.py index 8742575f..3d8ac9fd 100644 --- a/src/lithic/resources/financial_accounts/balances.py +++ b/src/lithic/resources/financial_accounts/balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/financial_accounts/financial_accounts.py b/src/lithic/resources/financial_accounts/financial_accounts.py index 4239d097..1085f439 100644 --- a/src/lithic/resources/financial_accounts/financial_accounts.py +++ b/src/lithic/resources/financial_accounts/financial_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/financial_accounts/financial_transactions.py b/src/lithic/resources/financial_accounts/financial_transactions.py index 4065f43c..b97b8063 100644 --- a/src/lithic/resources/financial_accounts/financial_transactions.py +++ b/src/lithic/resources/financial_accounts/financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/financial_accounts/statements/__init__.py b/src/lithic/resources/financial_accounts/statements/__init__.py index dbc3ec38..2882f28f 100644 --- a/src/lithic/resources/financial_accounts/statements/__init__.py +++ b/src/lithic/resources/financial_accounts/statements/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .line_items import ( LineItems, diff --git a/src/lithic/resources/financial_accounts/statements/line_items.py b/src/lithic/resources/financial_accounts/statements/line_items.py index a0080864..276a6df3 100644 --- a/src/lithic/resources/financial_accounts/statements/line_items.py +++ b/src/lithic/resources/financial_accounts/statements/line_items.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/financial_accounts/statements/statements.py b/src/lithic/resources/financial_accounts/statements/statements.py index 3af46fe2..ceda3dd1 100644 --- a/src/lithic/resources/financial_accounts/statements/statements.py +++ b/src/lithic/resources/financial_accounts/statements/statements.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/payments.py b/src/lithic/resources/payments.py index 49feee65..e5f081b6 100644 --- a/src/lithic/resources/payments.py +++ b/src/lithic/resources/payments.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/reports/__init__.py b/src/lithic/resources/reports/__init__.py index 64661c80..d7bcc5cf 100644 --- a/src/lithic/resources/reports/__init__.py +++ b/src/lithic/resources/reports/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .reports import ( Reports, diff --git a/src/lithic/resources/reports/reports.py b/src/lithic/resources/reports/reports.py index 6df9b401..ff735072 100644 --- a/src/lithic/resources/reports/reports.py +++ b/src/lithic/resources/reports/reports.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/reports/settlement.py b/src/lithic/resources/reports/settlement.py index 3010d53e..9119372a 100644 --- a/src/lithic/resources/reports/settlement.py +++ b/src/lithic/resources/reports/settlement.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/responder_endpoints.py b/src/lithic/resources/responder_endpoints.py index 6a7d74a0..94189cc5 100644 --- a/src/lithic/resources/responder_endpoints.py +++ b/src/lithic/resources/responder_endpoints.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/three_ds/__init__.py b/src/lithic/resources/three_ds/__init__.py index 32d6cf86..517059d5 100644 --- a/src/lithic/resources/three_ds/__init__.py +++ b/src/lithic/resources/three_ds/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .three_ds import ( ThreeDS, diff --git a/src/lithic/resources/three_ds/authentication.py b/src/lithic/resources/three_ds/authentication.py index 2f4d7f30..c1c6edf3 100644 --- a/src/lithic/resources/three_ds/authentication.py +++ b/src/lithic/resources/three_ds/authentication.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/three_ds/decisioning.py b/src/lithic/resources/three_ds/decisioning.py index 34349c91..b6fb2ee8 100644 --- a/src/lithic/resources/three_ds/decisioning.py +++ b/src/lithic/resources/three_ds/decisioning.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/three_ds/three_ds.py b/src/lithic/resources/three_ds/three_ds.py index a4597fb3..11217f56 100644 --- a/src/lithic/resources/three_ds/three_ds.py +++ b/src/lithic/resources/three_ds/three_ds.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/tokenization_decisioning.py b/src/lithic/resources/tokenization_decisioning.py index e05c3b57..189d6d7d 100644 --- a/src/lithic/resources/tokenization_decisioning.py +++ b/src/lithic/resources/tokenization_decisioning.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/tokenizations.py b/src/lithic/resources/tokenizations.py index 1dd4c6ee..9f929f39 100644 --- a/src/lithic/resources/tokenizations.py +++ b/src/lithic/resources/tokenizations.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/resources/transactions.py b/src/lithic/resources/transactions.py index b26dcb63..bbbd6dfd 100644 --- a/src/lithic/resources/transactions.py +++ b/src/lithic/resources/transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/__init__.py b/src/lithic/types/__init__.py index bc1b39f6..8ba6838f 100644 --- a/src/lithic/types/__init__.py +++ b/src/lithic/types/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account.py b/src/lithic/types/account.py index aa53d79c..fdab1999 100644 --- a/src/lithic/types/account.py +++ b/src/lithic/types/account.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from typing_extensions import Literal diff --git a/src/lithic/types/account_holder.py b/src/lithic/types/account_holder.py index 685da4ec..46db9ffd 100644 --- a/src/lithic/types/account_holder.py +++ b/src/lithic/types/account_holder.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/account_holder_create_params.py b/src/lithic/types/account_holder_create_params.py index eefc7324..0aa635fd 100644 --- a/src/lithic/types/account_holder_create_params.py +++ b/src/lithic/types/account_holder_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_holder_create_response.py b/src/lithic/types/account_holder_create_response.py index f98c7caa..9089a3f3 100644 --- a/src/lithic/types/account_holder_create_response.py +++ b/src/lithic/types/account_holder_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/account_holder_document.py b/src/lithic/types/account_holder_document.py index d703e7ea..0c79c229 100644 --- a/src/lithic/types/account_holder_document.py +++ b/src/lithic/types/account_holder_document.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from typing_extensions import Literal diff --git a/src/lithic/types/account_holder_list_documents_response.py b/src/lithic/types/account_holder_list_documents_response.py index 538f46b0..2855a48c 100644 --- a/src/lithic/types/account_holder_list_documents_response.py +++ b/src/lithic/types/account_holder_list_documents_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional diff --git a/src/lithic/types/account_holder_list_params.py b/src/lithic/types/account_holder_list_params.py index ebd5b9e6..4bb1046f 100644 --- a/src/lithic/types/account_holder_list_params.py +++ b/src/lithic/types/account_holder_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_holder_resubmit_params.py b/src/lithic/types/account_holder_resubmit_params.py index acf50828..e4db1691 100644 --- a/src/lithic/types/account_holder_resubmit_params.py +++ b/src/lithic/types/account_holder_resubmit_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_holder_update_params.py b/src/lithic/types/account_holder_update_params.py index cfe22fc2..d7e7dd62 100644 --- a/src/lithic/types/account_holder_update_params.py +++ b/src/lithic/types/account_holder_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_holder_update_response.py b/src/lithic/types/account_holder_update_response.py index c443f0c6..bc4c8ef0 100644 --- a/src/lithic/types/account_holder_update_response.py +++ b/src/lithic/types/account_holder_update_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/account_holder_upload_document_params.py b/src/lithic/types/account_holder_upload_document_params.py index 380e8804..17cf0271 100644 --- a/src/lithic/types/account_holder_upload_document_params.py +++ b/src/lithic/types/account_holder_upload_document_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_list_params.py b/src/lithic/types/account_list_params.py index ff8f3f68..44e75c2c 100644 --- a/src/lithic/types/account_list_params.py +++ b/src/lithic/types/account_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/account_spend_limits.py b/src/lithic/types/account_spend_limits.py index 0e74a8f5..efd91247 100644 --- a/src/lithic/types/account_spend_limits.py +++ b/src/lithic/types/account_spend_limits.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/account_update_params.py b/src/lithic/types/account_update_params.py index 3beb9101..c22b39f8 100644 --- a/src/lithic/types/account_update_params.py +++ b/src/lithic/types/account_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/accounts/__init__.py b/src/lithic/types/accounts/__init__.py index 0eb7dec1..02425dff 100644 --- a/src/lithic/types/accounts/__init__.py +++ b/src/lithic/types/accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/accounts/credit_configuration_update_params.py b/src/lithic/types/accounts/credit_configuration_update_params.py index f5d74b47..bab6d8a2 100644 --- a/src/lithic/types/accounts/credit_configuration_update_params.py +++ b/src/lithic/types/accounts/credit_configuration_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/aggregate_balance.py b/src/lithic/types/aggregate_balance.py index 32829ace..ad743bb3 100644 --- a/src/lithic/types/aggregate_balance.py +++ b/src/lithic/types/aggregate_balance.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime from typing_extensions import Literal diff --git a/src/lithic/types/aggregate_balance_list_params.py b/src/lithic/types/aggregate_balance_list_params.py index c093df18..d098e821 100644 --- a/src/lithic/types/aggregate_balance_list_params.py +++ b/src/lithic/types/aggregate_balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/api_status.py b/src/lithic/types/api_status.py index 7c7a3750..180f1470 100644 --- a/src/lithic/types/api_status.py +++ b/src/lithic/types/api_status.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/auth_rule.py b/src/lithic/types/auth_rule.py index 2017f101..52d9d9d7 100644 --- a/src/lithic/types/auth_rule.py +++ b/src/lithic/types/auth_rule.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from typing_extensions import Literal diff --git a/src/lithic/types/auth_rule_apply_params.py b/src/lithic/types/auth_rule_apply_params.py index 9cb97db5..0d9e3962 100644 --- a/src/lithic/types/auth_rule_apply_params.py +++ b/src/lithic/types/auth_rule_apply_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/auth_rule_create_params.py b/src/lithic/types/auth_rule_create_params.py index 308a1eab..e48cecdd 100644 --- a/src/lithic/types/auth_rule_create_params.py +++ b/src/lithic/types/auth_rule_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/auth_rule_list_params.py b/src/lithic/types/auth_rule_list_params.py index 581c186f..5db8d04d 100644 --- a/src/lithic/types/auth_rule_list_params.py +++ b/src/lithic/types/auth_rule_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/auth_rule_remove_params.py b/src/lithic/types/auth_rule_remove_params.py index 82b23227..df4a81b1 100644 --- a/src/lithic/types/auth_rule_remove_params.py +++ b/src/lithic/types/auth_rule_remove_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/auth_rule_remove_response.py b/src/lithic/types/auth_rule_remove_response.py index 13961e1b..284f7852 100644 --- a/src/lithic/types/auth_rule_remove_response.py +++ b/src/lithic/types/auth_rule_remove_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional diff --git a/src/lithic/types/auth_rule_retrieve_response.py b/src/lithic/types/auth_rule_retrieve_response.py index 31e16484..5c355591 100644 --- a/src/lithic/types/auth_rule_retrieve_response.py +++ b/src/lithic/types/auth_rule_retrieve_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional diff --git a/src/lithic/types/auth_rule_update_params.py b/src/lithic/types/auth_rule_update_params.py index 0503c6d9..020abe84 100644 --- a/src/lithic/types/auth_rule_update_params.py +++ b/src/lithic/types/auth_rule_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/auth_stream_secret.py b/src/lithic/types/auth_stream_secret.py index df85dc69..700172ab 100644 --- a/src/lithic/types/auth_stream_secret.py +++ b/src/lithic/types/auth_stream_secret.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/balance.py b/src/lithic/types/balance.py index edc571e1..7c74a3b7 100644 --- a/src/lithic/types/balance.py +++ b/src/lithic/types/balance.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime from typing_extensions import Literal diff --git a/src/lithic/types/balance_list_params.py b/src/lithic/types/balance_list_params.py index 94a52f3a..a8ad5324 100644 --- a/src/lithic/types/balance_list_params.py +++ b/src/lithic/types/balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/business_account.py b/src/lithic/types/business_account.py index 2203e0c1..7168415d 100644 --- a/src/lithic/types/business_account.py +++ b/src/lithic/types/business_account.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/card.py b/src/lithic/types/card.py index 65a45b61..9987e8a7 100644 --- a/src/lithic/types/card.py +++ b/src/lithic/types/card.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/card_create_params.py b/src/lithic/types/card_create_params.py index 02ff4c76..fec33966 100644 --- a/src/lithic/types/card_create_params.py +++ b/src/lithic/types/card_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_embed_params.py b/src/lithic/types/card_embed_params.py index 97b9c764..7e21ab6d 100644 --- a/src/lithic/types/card_embed_params.py +++ b/src/lithic/types/card_embed_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_embed_response.py b/src/lithic/types/card_embed_response.py index 5e480f33..cb0984c6 100644 --- a/src/lithic/types/card_embed_response.py +++ b/src/lithic/types/card_embed_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __all__ = ["CardEmbedResponse"] diff --git a/src/lithic/types/card_list_params.py b/src/lithic/types/card_list_params.py index df74475a..13dc9e53 100644 --- a/src/lithic/types/card_list_params.py +++ b/src/lithic/types/card_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_product_credit_detail_response.py b/src/lithic/types/card_product_credit_detail_response.py index 025a73b5..4a23027f 100644 --- a/src/lithic/types/card_product_credit_detail_response.py +++ b/src/lithic/types/card_product_credit_detail_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .._models import BaseModel diff --git a/src/lithic/types/card_program.py b/src/lithic/types/card_program.py index 858502a1..4e0bc257 100644 --- a/src/lithic/types/card_program.py +++ b/src/lithic/types/card_program.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime diff --git a/src/lithic/types/card_program_list_params.py b/src/lithic/types/card_program_list_params.py index 15257297..cfaa30b8 100644 --- a/src/lithic/types/card_program_list_params.py +++ b/src/lithic/types/card_program_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_provision_params.py b/src/lithic/types/card_provision_params.py index 7d36f620..b68c9ec7 100644 --- a/src/lithic/types/card_provision_params.py +++ b/src/lithic/types/card_provision_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_provision_response.py b/src/lithic/types/card_provision_response.py index 24c9fa70..fe565e0e 100644 --- a/src/lithic/types/card_provision_response.py +++ b/src/lithic/types/card_provision_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/card_reissue_params.py b/src/lithic/types/card_reissue_params.py index db5da95c..16738bc9 100644 --- a/src/lithic/types/card_reissue_params.py +++ b/src/lithic/types/card_reissue_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_renew_params.py b/src/lithic/types/card_renew_params.py index a3d6fd9a..af992de1 100644 --- a/src/lithic/types/card_renew_params.py +++ b/src/lithic/types/card_renew_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_search_by_pan_params.py b/src/lithic/types/card_search_by_pan_params.py index c9ec8935..2a0504ef 100644 --- a/src/lithic/types/card_search_by_pan_params.py +++ b/src/lithic/types/card_search_by_pan_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/card_spend_limits.py b/src/lithic/types/card_spend_limits.py index 782d2a7e..3e72cfc1 100644 --- a/src/lithic/types/card_spend_limits.py +++ b/src/lithic/types/card_spend_limits.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/card_update_params.py b/src/lithic/types/card_update_params.py index e5d52a7c..dbaab309 100644 --- a/src/lithic/types/card_update_params.py +++ b/src/lithic/types/card_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/cards/__init__.py b/src/lithic/types/cards/__init__.py index be61455b..63dfd5cb 100644 --- a/src/lithic/types/cards/__init__.py +++ b/src/lithic/types/cards/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/cards/aggregate_balance_list_params.py b/src/lithic/types/cards/aggregate_balance_list_params.py index 240c51a4..51b927dc 100644 --- a/src/lithic/types/cards/aggregate_balance_list_params.py +++ b/src/lithic/types/cards/aggregate_balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/cards/aggregate_balance_list_response.py b/src/lithic/types/cards/aggregate_balance_list_response.py index 849e15ab..2b300390 100644 --- a/src/lithic/types/cards/aggregate_balance_list_response.py +++ b/src/lithic/types/cards/aggregate_balance_list_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime diff --git a/src/lithic/types/cards/balance_list_params.py b/src/lithic/types/cards/balance_list_params.py index b80030a0..be24dded 100644 --- a/src/lithic/types/cards/balance_list_params.py +++ b/src/lithic/types/cards/balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/cards/financial_transaction_list_params.py b/src/lithic/types/cards/financial_transaction_list_params.py index 8c237520..04bdf49f 100644 --- a/src/lithic/types/cards/financial_transaction_list_params.py +++ b/src/lithic/types/cards/financial_transaction_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/digital_card_art.py b/src/lithic/types/digital_card_art.py index a414fa9f..3df0a6d2 100644 --- a/src/lithic/types/digital_card_art.py +++ b/src/lithic/types/digital_card_art.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime diff --git a/src/lithic/types/digital_card_art_list_params.py b/src/lithic/types/digital_card_art_list_params.py index eb3ed626..5233633f 100644 --- a/src/lithic/types/digital_card_art_list_params.py +++ b/src/lithic/types/digital_card_art_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute.py b/src/lithic/types/dispute.py index d9e5a863..e217d8e8 100644 --- a/src/lithic/types/dispute.py +++ b/src/lithic/types/dispute.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/dispute_create_params.py b/src/lithic/types/dispute_create_params.py index 4d981410..d515813b 100644 --- a/src/lithic/types/dispute_create_params.py +++ b/src/lithic/types/dispute_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_evidence.py b/src/lithic/types/dispute_evidence.py index a850e9b9..cd6f1958 100644 --- a/src/lithic/types/dispute_evidence.py +++ b/src/lithic/types/dispute_evidence.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime diff --git a/src/lithic/types/dispute_initiate_evidence_upload_params.py b/src/lithic/types/dispute_initiate_evidence_upload_params.py index 5ed6c9e0..29e610e7 100644 --- a/src/lithic/types/dispute_initiate_evidence_upload_params.py +++ b/src/lithic/types/dispute_initiate_evidence_upload_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_list_evidences_params.py b/src/lithic/types/dispute_list_evidences_params.py index c9b3f360..9c84266d 100644 --- a/src/lithic/types/dispute_list_evidences_params.py +++ b/src/lithic/types/dispute_list_evidences_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_list_params.py b/src/lithic/types/dispute_list_params.py index 6ad90569..fd12fdf5 100644 --- a/src/lithic/types/dispute_list_params.py +++ b/src/lithic/types/dispute_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/dispute_update_params.py b/src/lithic/types/dispute_update_params.py index 52b8ba74..de0b45aa 100644 --- a/src/lithic/types/dispute_update_params.py +++ b/src/lithic/types/dispute_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/event.py b/src/lithic/types/event.py index 035a2a26..7f3c7c2e 100644 --- a/src/lithic/types/event.py +++ b/src/lithic/types/event.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict from datetime import datetime diff --git a/src/lithic/types/event_list_attempts_params.py b/src/lithic/types/event_list_attempts_params.py index d2f594f6..cd087c03 100644 --- a/src/lithic/types/event_list_attempts_params.py +++ b/src/lithic/types/event_list_attempts_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/event_list_params.py b/src/lithic/types/event_list_params.py index e5eadac5..c38315cc 100644 --- a/src/lithic/types/event_list_params.py +++ b/src/lithic/types/event_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/event_subscription.py b/src/lithic/types/event_subscription.py index 28f8802f..c16a0bf0 100644 --- a/src/lithic/types/event_subscription.py +++ b/src/lithic/types/event_subscription.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from typing_extensions import Literal diff --git a/src/lithic/types/events/__init__.py b/src/lithic/types/events/__init__.py index aea612e2..afe8a307 100644 --- a/src/lithic/types/events/__init__.py +++ b/src/lithic/types/events/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_create_params.py b/src/lithic/types/events/subscription_create_params.py index acd89ebb..4f3cd23d 100644 --- a/src/lithic/types/events/subscription_create_params.py +++ b/src/lithic/types/events/subscription_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_list_attempts_params.py b/src/lithic/types/events/subscription_list_attempts_params.py index 00523a7d..ce3f2678 100644 --- a/src/lithic/types/events/subscription_list_attempts_params.py +++ b/src/lithic/types/events/subscription_list_attempts_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_list_params.py b/src/lithic/types/events/subscription_list_params.py index feb66ed8..bc25fa49 100644 --- a/src/lithic/types/events/subscription_list_params.py +++ b/src/lithic/types/events/subscription_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_recover_params.py b/src/lithic/types/events/subscription_recover_params.py index 46953dab..dcc4fe80 100644 --- a/src/lithic/types/events/subscription_recover_params.py +++ b/src/lithic/types/events/subscription_recover_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_replay_missing_params.py b/src/lithic/types/events/subscription_replay_missing_params.py index 06fd3b3d..ebb1a04a 100644 --- a/src/lithic/types/events/subscription_replay_missing_params.py +++ b/src/lithic/types/events/subscription_replay_missing_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_retrieve_secret_response.py b/src/lithic/types/events/subscription_retrieve_secret_response.py index 607372ad..467c4f8a 100644 --- a/src/lithic/types/events/subscription_retrieve_secret_response.py +++ b/src/lithic/types/events/subscription_retrieve_secret_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/events/subscription_send_simulated_example_params.py b/src/lithic/types/events/subscription_send_simulated_example_params.py index 6e62a6e8..0bad121b 100644 --- a/src/lithic/types/events/subscription_send_simulated_example_params.py +++ b/src/lithic/types/events/subscription_send_simulated_example_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/events/subscription_update_params.py b/src/lithic/types/events/subscription_update_params.py index c6143566..4f297f34 100644 --- a/src/lithic/types/events/subscription_update_params.py +++ b/src/lithic/types/events/subscription_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_account_address.py b/src/lithic/types/external_bank_account_address.py index 946a9e98..e755b393 100644 --- a/src/lithic/types/external_bank_account_address.py +++ b/src/lithic/types/external_bank_account_address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/external_bank_account_address_param.py b/src/lithic/types/external_bank_account_address_param.py index d057e56c..f201ef84 100644 --- a/src/lithic/types/external_bank_account_address_param.py +++ b/src/lithic/types/external_bank_account_address_param.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_account_create_params.py b/src/lithic/types/external_bank_account_create_params.py index 78168666..161546fb 100644 --- a/src/lithic/types/external_bank_account_create_params.py +++ b/src/lithic/types/external_bank_account_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_account_create_response.py b/src/lithic/types/external_bank_account_create_response.py index 593fd169..38315f71 100644 --- a/src/lithic/types/external_bank_account_create_response.py +++ b/src/lithic/types/external_bank_account_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/external_bank_account_list_params.py b/src/lithic/types/external_bank_account_list_params.py index 561b6808..fb82767e 100644 --- a/src/lithic/types/external_bank_account_list_params.py +++ b/src/lithic/types/external_bank_account_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_account_list_response.py b/src/lithic/types/external_bank_account_list_response.py index 867b7937..78d0082d 100644 --- a/src/lithic/types/external_bank_account_list_response.py +++ b/src/lithic/types/external_bank_account_list_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/external_bank_account_retrieve_response.py b/src/lithic/types/external_bank_account_retrieve_response.py index 7bb09881..3c2aa5af 100644 --- a/src/lithic/types/external_bank_account_retrieve_response.py +++ b/src/lithic/types/external_bank_account_retrieve_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/external_bank_account_retry_micro_deposits_response.py b/src/lithic/types/external_bank_account_retry_micro_deposits_response.py index be343ea7..1bd7bef4 100644 --- a/src/lithic/types/external_bank_account_retry_micro_deposits_response.py +++ b/src/lithic/types/external_bank_account_retry_micro_deposits_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/external_bank_account_update_params.py b/src/lithic/types/external_bank_account_update_params.py index 68ac4e2d..5feadf0c 100644 --- a/src/lithic/types/external_bank_account_update_params.py +++ b/src/lithic/types/external_bank_account_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_account_update_response.py b/src/lithic/types/external_bank_account_update_response.py index 85d617fe..a0900ac8 100644 --- a/src/lithic/types/external_bank_account_update_response.py +++ b/src/lithic/types/external_bank_account_update_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/external_bank_accounts/__init__.py b/src/lithic/types/external_bank_accounts/__init__.py index 97d834ff..b30011e2 100644 --- a/src/lithic/types/external_bank_accounts/__init__.py +++ b/src/lithic/types/external_bank_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py b/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py index 3f0ac427..44f17a67 100644 --- a/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py +++ b/src/lithic/types/external_bank_accounts/micro_deposit_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py b/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py index b101ce52..2b2f73d4 100644 --- a/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py +++ b/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/financial_account.py b/src/lithic/types/financial_account.py index 9c5cee34..6a4a539e 100644 --- a/src/lithic/types/financial_account.py +++ b/src/lithic/types/financial_account.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime diff --git a/src/lithic/types/financial_account_create_params.py b/src/lithic/types/financial_account_create_params.py index f854cb14..89f33378 100644 --- a/src/lithic/types/financial_account_create_params.py +++ b/src/lithic/types/financial_account_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_account_list_params.py b/src/lithic/types/financial_account_list_params.py index 86cb0d10..d465f5be 100644 --- a/src/lithic/types/financial_account_list_params.py +++ b/src/lithic/types/financial_account_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_account_update_params.py b/src/lithic/types/financial_account_update_params.py index b2bd5e08..41bb68a8 100644 --- a/src/lithic/types/financial_account_update_params.py +++ b/src/lithic/types/financial_account_update_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/__init__.py b/src/lithic/types/financial_accounts/__init__.py index 048f281c..56d3191e 100644 --- a/src/lithic/types/financial_accounts/__init__.py +++ b/src/lithic/types/financial_accounts/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/balance_list_params.py b/src/lithic/types/financial_accounts/balance_list_params.py index b80030a0..be24dded 100644 --- a/src/lithic/types/financial_accounts/balance_list_params.py +++ b/src/lithic/types/financial_accounts/balance_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/financial_transaction_list_params.py b/src/lithic/types/financial_accounts/financial_transaction_list_params.py index 68018ec2..2cace051 100644 --- a/src/lithic/types/financial_accounts/financial_transaction_list_params.py +++ b/src/lithic/types/financial_accounts/financial_transaction_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/statement.py b/src/lithic/types/financial_accounts/statement.py index b30afc8a..a30567ec 100644 --- a/src/lithic/types/financial_accounts/statement.py +++ b/src/lithic/types/financial_accounts/statement.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import date, datetime diff --git a/src/lithic/types/financial_accounts/statement_list_params.py b/src/lithic/types/financial_accounts/statement_list_params.py index 7006945c..5d79c057 100644 --- a/src/lithic/types/financial_accounts/statement_list_params.py +++ b/src/lithic/types/financial_accounts/statement_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/statements/__init__.py b/src/lithic/types/financial_accounts/statements/__init__.py index 1c4ae2ca..5acba760 100644 --- a/src/lithic/types/financial_accounts/statements/__init__.py +++ b/src/lithic/types/financial_accounts/statements/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/statements/line_item_list_params.py b/src/lithic/types/financial_accounts/statements/line_item_list_params.py index 5c585b1c..d5df57f3 100644 --- a/src/lithic/types/financial_accounts/statements/line_item_list_params.py +++ b/src/lithic/types/financial_accounts/statements/line_item_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/financial_accounts/statements/line_item_list_response.py b/src/lithic/types/financial_accounts/statements/line_item_list_response.py index ab713ba3..62f7c5d8 100644 --- a/src/lithic/types/financial_accounts/statements/line_item_list_response.py +++ b/src/lithic/types/financial_accounts/statements/line_item_list_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import date, datetime diff --git a/src/lithic/types/financial_transaction.py b/src/lithic/types/financial_transaction.py index 6936858a..72bd5548 100644 --- a/src/lithic/types/financial_transaction.py +++ b/src/lithic/types/financial_transaction.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/message_attempt.py b/src/lithic/types/message_attempt.py index 7218ab67..a3443db0 100644 --- a/src/lithic/types/message_attempt.py +++ b/src/lithic/types/message_attempt.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from datetime import datetime from typing_extensions import Literal diff --git a/src/lithic/types/owner_type.py b/src/lithic/types/owner_type.py index 9623609c..f30ad4a4 100644 --- a/src/lithic/types/owner_type.py +++ b/src/lithic/types/owner_type.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing_extensions import Literal diff --git a/src/lithic/types/payment.py b/src/lithic/types/payment.py index ea0047c7..ac07fd23 100644 --- a/src/lithic/types/payment.py +++ b/src/lithic/types/payment.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from typing_extensions import Literal diff --git a/src/lithic/types/payment_create_params.py b/src/lithic/types/payment_create_params.py index 8916fa28..1c536ec6 100644 --- a/src/lithic/types/payment_create_params.py +++ b/src/lithic/types/payment_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/payment_create_response.py b/src/lithic/types/payment_create_response.py index 19acafe9..446c5353 100644 --- a/src/lithic/types/payment_create_response.py +++ b/src/lithic/types/payment_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/payment_list_params.py b/src/lithic/types/payment_list_params.py index 11dcd7b7..80422355 100644 --- a/src/lithic/types/payment_list_params.py +++ b/src/lithic/types/payment_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/payment_retry_response.py b/src/lithic/types/payment_retry_response.py index e3e0fbce..5bb111b2 100644 --- a/src/lithic/types/payment_retry_response.py +++ b/src/lithic/types/payment_retry_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/payment_simulate_release_params.py b/src/lithic/types/payment_simulate_release_params.py index 452b2ac8..ce1c7330 100644 --- a/src/lithic/types/payment_simulate_release_params.py +++ b/src/lithic/types/payment_simulate_release_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/payment_simulate_release_response.py b/src/lithic/types/payment_simulate_release_response.py index 0326e62f..8a00d46b 100644 --- a/src/lithic/types/payment_simulate_release_response.py +++ b/src/lithic/types/payment_simulate_release_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from typing_extensions import Literal diff --git a/src/lithic/types/payment_simulate_return_params.py b/src/lithic/types/payment_simulate_return_params.py index d4d094c9..a378e21f 100644 --- a/src/lithic/types/payment_simulate_return_params.py +++ b/src/lithic/types/payment_simulate_return_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/payment_simulate_return_response.py b/src/lithic/types/payment_simulate_return_response.py index 9ceb94e9..2be6d12e 100644 --- a/src/lithic/types/payment_simulate_return_response.py +++ b/src/lithic/types/payment_simulate_return_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from typing_extensions import Literal diff --git a/src/lithic/types/reports/__init__.py b/src/lithic/types/reports/__init__.py index b7d013b8..1d825b95 100644 --- a/src/lithic/types/reports/__init__.py +++ b/src/lithic/types/reports/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/reports/settlement_list_details_params.py b/src/lithic/types/reports/settlement_list_details_params.py index 8f142e31..003f65d0 100644 --- a/src/lithic/types/reports/settlement_list_details_params.py +++ b/src/lithic/types/reports/settlement_list_details_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_check_status_params.py b/src/lithic/types/responder_endpoint_check_status_params.py index bc4391ee..f21aef7e 100644 --- a/src/lithic/types/responder_endpoint_check_status_params.py +++ b/src/lithic/types/responder_endpoint_check_status_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_create_params.py b/src/lithic/types/responder_endpoint_create_params.py index ddda6e7d..36fe9caf 100644 --- a/src/lithic/types/responder_endpoint_create_params.py +++ b/src/lithic/types/responder_endpoint_create_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_create_response.py b/src/lithic/types/responder_endpoint_create_response.py index 24f83e30..198faf4e 100644 --- a/src/lithic/types/responder_endpoint_create_response.py +++ b/src/lithic/types/responder_endpoint_create_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/responder_endpoint_delete_params.py b/src/lithic/types/responder_endpoint_delete_params.py index e3482dab..59def61c 100644 --- a/src/lithic/types/responder_endpoint_delete_params.py +++ b/src/lithic/types/responder_endpoint_delete_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/responder_endpoint_status.py b/src/lithic/types/responder_endpoint_status.py index a6fb9929..f2a29aa1 100644 --- a/src/lithic/types/responder_endpoint_status.py +++ b/src/lithic/types/responder_endpoint_status.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/settlement_detail.py b/src/lithic/types/settlement_detail.py index d1ec8ce1..975a87e4 100644 --- a/src/lithic/types/settlement_detail.py +++ b/src/lithic/types/settlement_detail.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/settlement_report.py b/src/lithic/types/settlement_report.py index 432433ba..2704c9b0 100644 --- a/src/lithic/types/settlement_report.py +++ b/src/lithic/types/settlement_report.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/settlement_summary_details.py b/src/lithic/types/settlement_summary_details.py index 7da0a02c..7f9bd7ae 100644 --- a/src/lithic/types/settlement_summary_details.py +++ b/src/lithic/types/settlement_summary_details.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from typing_extensions import Literal diff --git a/src/lithic/types/shared/__init__.py b/src/lithic/types/shared/__init__.py index 7646a183..3cd36cfe 100644 --- a/src/lithic/types/shared/__init__.py +++ b/src/lithic/types/shared/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .address import Address as Address from .carrier import Carrier as Carrier diff --git a/src/lithic/types/shared/address.py b/src/lithic/types/shared/address.py index cabf52f7..d662829b 100644 --- a/src/lithic/types/shared/address.py +++ b/src/lithic/types/shared/address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/shared/carrier.py b/src/lithic/types/shared/carrier.py index 40192c24..591cfb25 100644 --- a/src/lithic/types/shared/carrier.py +++ b/src/lithic/types/shared/carrier.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/shared/shipping_address.py b/src/lithic/types/shared/shipping_address.py index 13ef0949..c9cebce7 100644 --- a/src/lithic/types/shared/shipping_address.py +++ b/src/lithic/types/shared/shipping_address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/shared_params/__init__.py b/src/lithic/types/shared_params/__init__.py index 7646a183..3cd36cfe 100644 --- a/src/lithic/types/shared_params/__init__.py +++ b/src/lithic/types/shared_params/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .address import Address as Address from .carrier import Carrier as Carrier diff --git a/src/lithic/types/shared_params/address.py b/src/lithic/types/shared_params/address.py index f3bff9ee..3b80f19c 100644 --- a/src/lithic/types/shared_params/address.py +++ b/src/lithic/types/shared_params/address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/shared_params/carrier.py b/src/lithic/types/shared_params/carrier.py index caf5e6fa..9c644c89 100644 --- a/src/lithic/types/shared_params/carrier.py +++ b/src/lithic/types/shared_params/carrier.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/shared_params/shipping_address.py b/src/lithic/types/shared_params/shipping_address.py index 96f980bc..a25aaf2c 100644 --- a/src/lithic/types/shared_params/shipping_address.py +++ b/src/lithic/types/shared_params/shipping_address.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/spend_limit_duration.py b/src/lithic/types/spend_limit_duration.py index c299270a..f8f46cb7 100644 --- a/src/lithic/types/spend_limit_duration.py +++ b/src/lithic/types/spend_limit_duration.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing_extensions import Literal diff --git a/src/lithic/types/three_ds/__init__.py b/src/lithic/types/three_ds/__init__.py index d73b4f7c..9aff52b0 100644 --- a/src/lithic/types/three_ds/__init__.py +++ b/src/lithic/types/three_ds/__init__.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/three_ds/authentication_retrieve_response.py b/src/lithic/types/three_ds/authentication_retrieve_response.py index fcaf0626..effabdfe 100644 --- a/src/lithic/types/three_ds/authentication_retrieve_response.py +++ b/src/lithic/types/three_ds/authentication_retrieve_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional from datetime import datetime diff --git a/src/lithic/types/three_ds/authentication_simulate_params.py b/src/lithic/types/three_ds/authentication_simulate_params.py index 5281b070..fa01f41b 100644 --- a/src/lithic/types/three_ds/authentication_simulate_params.py +++ b/src/lithic/types/three_ds/authentication_simulate_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/three_ds/authentication_simulate_response.py b/src/lithic/types/three_ds/authentication_simulate_response.py index ac5664ba..503fdce2 100644 --- a/src/lithic/types/three_ds/authentication_simulate_response.py +++ b/src/lithic/types/three_ds/authentication_simulate_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py b/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py index a14c545f..28f2569c 100644 --- a/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py +++ b/src/lithic/types/three_ds/decisioning_retrieve_secret_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/tokenization.py b/src/lithic/types/tokenization.py index f2511e31..38500d69 100644 --- a/src/lithic/types/tokenization.py +++ b/src/lithic/types/tokenization.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/tokenization_decisioning_rotate_secret_response.py b/src/lithic/types/tokenization_decisioning_rotate_secret_response.py index 21182641..e1428626 100644 --- a/src/lithic/types/tokenization_decisioning_rotate_secret_response.py +++ b/src/lithic/types/tokenization_decisioning_rotate_secret_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/tokenization_list_params.py b/src/lithic/types/tokenization_list_params.py index 41901c2b..9b41353e 100644 --- a/src/lithic/types/tokenization_list_params.py +++ b/src/lithic/types/tokenization_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/tokenization_retrieve_response.py b/src/lithic/types/tokenization_retrieve_response.py index e0bc3a55..95f133a1 100644 --- a/src/lithic/types/tokenization_retrieve_response.py +++ b/src/lithic/types/tokenization_retrieve_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/tokenization_secret.py b/src/lithic/types/tokenization_secret.py index d91507b2..d699d84b 100644 --- a/src/lithic/types/tokenization_secret.py +++ b/src/lithic/types/tokenization_secret.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/tokenization_simulate_params.py b/src/lithic/types/tokenization_simulate_params.py index f5b20604..f7a0fae1 100644 --- a/src/lithic/types/tokenization_simulate_params.py +++ b/src/lithic/types/tokenization_simulate_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/tokenization_simulate_response.py b/src/lithic/types/tokenization_simulate_response.py index e5fdd689..a991e422 100644 --- a/src/lithic/types/tokenization_simulate_response.py +++ b/src/lithic/types/tokenization_simulate_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional diff --git a/src/lithic/types/transaction.py b/src/lithic/types/transaction.py index f5b01ed6..3d129061 100644 --- a/src/lithic/types/transaction.py +++ b/src/lithic/types/transaction.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional from datetime import datetime diff --git a/src/lithic/types/transaction_list_params.py b/src/lithic/types/transaction_list_params.py index f9d88f22..01d3e01e 100644 --- a/src/lithic/types/transaction_list_params.py +++ b/src/lithic/types/transaction_list_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_authorization_advice_params.py b/src/lithic/types/transaction_simulate_authorization_advice_params.py index da7f5307..503ec9d5 100644 --- a/src/lithic/types/transaction_simulate_authorization_advice_params.py +++ b/src/lithic/types/transaction_simulate_authorization_advice_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_authorization_advice_response.py b/src/lithic/types/transaction_simulate_authorization_advice_response.py index 7c48d3e9..4e8016cb 100644 --- a/src/lithic/types/transaction_simulate_authorization_advice_response.py +++ b/src/lithic/types/transaction_simulate_authorization_advice_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_authorization_params.py b/src/lithic/types/transaction_simulate_authorization_params.py index 8d0c9104..912f79d7 100644 --- a/src/lithic/types/transaction_simulate_authorization_params.py +++ b/src/lithic/types/transaction_simulate_authorization_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_authorization_response.py b/src/lithic/types/transaction_simulate_authorization_response.py index 0766f63d..859424ac 100644 --- a/src/lithic/types/transaction_simulate_authorization_response.py +++ b/src/lithic/types/transaction_simulate_authorization_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_clearing_params.py b/src/lithic/types/transaction_simulate_clearing_params.py index dcf7a984..94475c8e 100644 --- a/src/lithic/types/transaction_simulate_clearing_params.py +++ b/src/lithic/types/transaction_simulate_clearing_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_clearing_response.py b/src/lithic/types/transaction_simulate_clearing_response.py index cd92cf8a..91af533f 100644 --- a/src/lithic/types/transaction_simulate_clearing_response.py +++ b/src/lithic/types/transaction_simulate_clearing_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_credit_authorization_params.py b/src/lithic/types/transaction_simulate_credit_authorization_params.py index 6f165543..a25b00f0 100644 --- a/src/lithic/types/transaction_simulate_credit_authorization_params.py +++ b/src/lithic/types/transaction_simulate_credit_authorization_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_credit_authorization_response.py b/src/lithic/types/transaction_simulate_credit_authorization_response.py index 36d65d35..e50b3889 100644 --- a/src/lithic/types/transaction_simulate_credit_authorization_response.py +++ b/src/lithic/types/transaction_simulate_credit_authorization_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_return_params.py b/src/lithic/types/transaction_simulate_return_params.py index 46684e01..7487fe82 100644 --- a/src/lithic/types/transaction_simulate_return_params.py +++ b/src/lithic/types/transaction_simulate_return_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_return_response.py b/src/lithic/types/transaction_simulate_return_response.py index c898f734..6f67e632 100644 --- a/src/lithic/types/transaction_simulate_return_response.py +++ b/src/lithic/types/transaction_simulate_return_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_return_reversal_params.py b/src/lithic/types/transaction_simulate_return_reversal_params.py index 7b8ee082..f67a31d4 100644 --- a/src/lithic/types/transaction_simulate_return_reversal_params.py +++ b/src/lithic/types/transaction_simulate_return_reversal_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_return_reversal_response.py b/src/lithic/types/transaction_simulate_return_reversal_response.py index 30b4a9e0..abc9edd2 100644 --- a/src/lithic/types/transaction_simulate_return_reversal_response.py +++ b/src/lithic/types/transaction_simulate_return_reversal_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/transaction_simulate_void_params.py b/src/lithic/types/transaction_simulate_void_params.py index 95dd6e2f..dc796f1f 100644 --- a/src/lithic/types/transaction_simulate_void_params.py +++ b/src/lithic/types/transaction_simulate_void_params.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/src/lithic/types/transaction_simulate_void_response.py b/src/lithic/types/transaction_simulate_void_response.py index a110202f..df321bf9 100644 --- a/src/lithic/types/transaction_simulate_void_response.py +++ b/src/lithic/types/transaction_simulate_void_response.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional diff --git a/src/lithic/types/verification_method.py b/src/lithic/types/verification_method.py index 9972e037..948ab7e3 100644 --- a/src/lithic/types/verification_method.py +++ b/src/lithic/types/verification_method.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing_extensions import Literal diff --git a/tests/__init__.py b/tests/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/__init__.py +++ b/tests/api_resources/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/accounts/__init__.py b/tests/api_resources/accounts/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/accounts/__init__.py +++ b/tests/api_resources/accounts/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/accounts/test_credit_configurations.py b/tests/api_resources/accounts/test_credit_configurations.py index e6e1c3f9..e9829089 100644 --- a/tests/api_resources/accounts/test_credit_configurations.py +++ b/tests/api_resources/accounts/test_credit_configurations.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/cards/__init__.py b/tests/api_resources/cards/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/cards/__init__.py +++ b/tests/api_resources/cards/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/cards/test_aggregate_balances.py b/tests/api_resources/cards/test_aggregate_balances.py index 57e508e7..bc68754a 100644 --- a/tests/api_resources/cards/test_aggregate_balances.py +++ b/tests/api_resources/cards/test_aggregate_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/cards/test_balances.py b/tests/api_resources/cards/test_balances.py index 2fab472e..4bc89e5a 100644 --- a/tests/api_resources/cards/test_balances.py +++ b/tests/api_resources/cards/test_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/cards/test_financial_transactions.py b/tests/api_resources/cards/test_financial_transactions.py index 3d580cee..31c2119d 100644 --- a/tests/api_resources/cards/test_financial_transactions.py +++ b/tests/api_resources/cards/test_financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/events/__init__.py b/tests/api_resources/events/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/events/__init__.py +++ b/tests/api_resources/events/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/events/test_subscriptions.py b/tests/api_resources/events/test_subscriptions.py index adab0a0d..e3e32f19 100644 --- a/tests/api_resources/events/test_subscriptions.py +++ b/tests/api_resources/events/test_subscriptions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/external_bank_accounts/__init__.py b/tests/api_resources/external_bank_accounts/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/external_bank_accounts/__init__.py +++ b/tests/api_resources/external_bank_accounts/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/external_bank_accounts/test_micro_deposits.py b/tests/api_resources/external_bank_accounts/test_micro_deposits.py index 98d2ef7b..aff82ad3 100644 --- a/tests/api_resources/external_bank_accounts/test_micro_deposits.py +++ b/tests/api_resources/external_bank_accounts/test_micro_deposits.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/financial_accounts/__init__.py b/tests/api_resources/financial_accounts/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/financial_accounts/__init__.py +++ b/tests/api_resources/financial_accounts/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/financial_accounts/statements/__init__.py b/tests/api_resources/financial_accounts/statements/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/financial_accounts/statements/__init__.py +++ b/tests/api_resources/financial_accounts/statements/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/financial_accounts/statements/test_line_items.py b/tests/api_resources/financial_accounts/statements/test_line_items.py index b6be34e2..1dc777d8 100644 --- a/tests/api_resources/financial_accounts/statements/test_line_items.py +++ b/tests/api_resources/financial_accounts/statements/test_line_items.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/financial_accounts/test_balances.py b/tests/api_resources/financial_accounts/test_balances.py index f5b2f846..3c58ae5e 100644 --- a/tests/api_resources/financial_accounts/test_balances.py +++ b/tests/api_resources/financial_accounts/test_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/financial_accounts/test_financial_transactions.py b/tests/api_resources/financial_accounts/test_financial_transactions.py index 4d97ef36..ca24abef 100644 --- a/tests/api_resources/financial_accounts/test_financial_transactions.py +++ b/tests/api_resources/financial_accounts/test_financial_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/financial_accounts/test_statements.py b/tests/api_resources/financial_accounts/test_statements.py index 7af808f3..c4d4c38a 100644 --- a/tests/api_resources/financial_accounts/test_statements.py +++ b/tests/api_resources/financial_accounts/test_statements.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/reports/__init__.py b/tests/api_resources/reports/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/reports/__init__.py +++ b/tests/api_resources/reports/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/reports/test_settlement.py b/tests/api_resources/reports/test_settlement.py index 5ee7842b..2162ac4e 100644 --- a/tests/api_resources/reports/test_settlement.py +++ b/tests/api_resources/reports/test_settlement.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_account_holders.py b/tests/api_resources/test_account_holders.py index 76c01a0c..564c8ceb 100644 --- a/tests/api_resources/test_account_holders.py +++ b/tests/api_resources/test_account_holders.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py index be99c07d..c1b7dbe8 100644 --- a/tests/api_resources/test_accounts.py +++ b/tests/api_resources/test_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_aggregate_balances.py b/tests/api_resources/test_aggregate_balances.py index 5065d562..ab692037 100644 --- a/tests/api_resources/test_aggregate_balances.py +++ b/tests/api_resources/test_aggregate_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_auth_rules.py b/tests/api_resources/test_auth_rules.py index ddbcdcb1..6a8079e8 100644 --- a/tests/api_resources/test_auth_rules.py +++ b/tests/api_resources/test_auth_rules.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_auth_stream_enrollment.py b/tests/api_resources/test_auth_stream_enrollment.py index ac3763d5..6405a48a 100644 --- a/tests/api_resources/test_auth_stream_enrollment.py +++ b/tests/api_resources/test_auth_stream_enrollment.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_balances.py b/tests/api_resources/test_balances.py index b8a6b28a..36317c12 100644 --- a/tests/api_resources/test_balances.py +++ b/tests/api_resources/test_balances.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_card_product.py b/tests/api_resources/test_card_product.py index 12bceacc..16e4af0f 100644 --- a/tests/api_resources/test_card_product.py +++ b/tests/api_resources/test_card_product.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_card_programs.py b/tests/api_resources/test_card_programs.py index 72cb866b..233ad944 100644 --- a/tests/api_resources/test_card_programs.py +++ b/tests/api_resources/test_card_programs.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index a01331e0..56359153 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_digital_card_art.py b/tests/api_resources/test_digital_card_art.py index 9d002777..7108e72a 100644 --- a/tests/api_resources/test_digital_card_art.py +++ b/tests/api_resources/test_digital_card_art.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_disputes.py b/tests/api_resources/test_disputes.py index c1db18b7..b5d64f5e 100644 --- a/tests/api_resources/test_disputes.py +++ b/tests/api_resources/test_disputes.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py index 893dc631..1c07a9d1 100644 --- a/tests/api_resources/test_events.py +++ b/tests/api_resources/test_events.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_external_bank_accounts.py b/tests/api_resources/test_external_bank_accounts.py index 45f0d235..d76a2794 100644 --- a/tests/api_resources/test_external_bank_accounts.py +++ b/tests/api_resources/test_external_bank_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_financial_accounts.py b/tests/api_resources/test_financial_accounts.py index 32d7e033..a7669d50 100644 --- a/tests/api_resources/test_financial_accounts.py +++ b/tests/api_resources/test_financial_accounts.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_payments.py b/tests/api_resources/test_payments.py index 72f04978..23ddfb38 100644 --- a/tests/api_resources/test_payments.py +++ b/tests/api_resources/test_payments.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_responder_endpoints.py b/tests/api_resources/test_responder_endpoints.py index e5d9e506..53555059 100644 --- a/tests/api_resources/test_responder_endpoints.py +++ b/tests/api_resources/test_responder_endpoints.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_tokenization_decisioning.py b/tests/api_resources/test_tokenization_decisioning.py index 7ae71bbc..b20f6cfa 100644 --- a/tests/api_resources/test_tokenization_decisioning.py +++ b/tests/api_resources/test_tokenization_decisioning.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_tokenizations.py b/tests/api_resources/test_tokenizations.py index 1d48cc9a..3a42752b 100644 --- a/tests/api_resources/test_tokenizations.py +++ b/tests/api_resources/test_tokenizations.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_top_level.py b/tests/api_resources/test_top_level.py index 6f5c8ba6..a55a0d62 100644 --- a/tests/api_resources/test_top_level.py +++ b/tests/api_resources/test_top_level.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/test_transactions.py b/tests/api_resources/test_transactions.py index 4e7b9003..266a1175 100644 --- a/tests/api_resources/test_transactions.py +++ b/tests/api_resources/test_transactions.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/three_ds/__init__.py b/tests/api_resources/three_ds/__init__.py index 1016754e..fd8019a9 100644 --- a/tests/api_resources/three_ds/__init__.py +++ b/tests/api_resources/three_ds/__init__.py @@ -1 +1 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/three_ds/test_authentication.py b/tests/api_resources/three_ds/test_authentication.py index f4a9d774..e1b1867b 100644 --- a/tests/api_resources/three_ds/test_authentication.py +++ b/tests/api_resources/three_ds/test_authentication.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/api_resources/three_ds/test_decisioning.py b/tests/api_resources/three_ds/test_decisioning.py index cc15ea98..66c8db0a 100644 --- a/tests/api_resources/three_ds/test_decisioning.py +++ b/tests/api_resources/three_ds/test_decisioning.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations diff --git a/tests/test_client.py b/tests/test_client.py index e0918f0d..cc143b5b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,4 @@ -# File generated from our OpenAPI spec by Stainless. +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations From 1294d5b6766a15dd5956395324dd38ded94adcb0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:24:38 +0000 Subject: [PATCH 21/26] chore(internal): loosen input type for util function (#392) --- src/lithic/_models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lithic/_models.py b/src/lithic/_models.py index 16697353..35a23a95 100644 --- a/src/lithic/_models.py +++ b/src/lithic/_models.py @@ -290,11 +290,15 @@ def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericMo return issubclass(origin, BaseModel) or issubclass(origin, GenericModel) -def construct_type(*, value: object, type_: type) -> object: +def construct_type(*, value: object, type_: object) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. """ + # we allow `object` as the input type because otherwise, passing things like + # `Literal['value']` will be reported as a type error by type checkers + type_ = cast("type[object]", type_) + # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): meta = get_args(type_)[1:] From 0fb722fe212c67cdcab814ba68158c74607c94f6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 03:26:04 +0000 Subject: [PATCH 22/26] docs(readme): document how to make undocumented requests (#393) --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 41b0e96a..d5a8fd74 100644 --- a/README.md +++ b/README.md @@ -361,6 +361,41 @@ with client.cards.with_streaming_response.create( The context manager is required so that the response will reliably be closed. +### Making custom/undocumented requests + +This library is typed for convenient access the documented API. + +If you need to access undocumented endpoints, params, or response properties, the library can still be used. + +#### Undocumented endpoints + +To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other +http verbs. Options on the client will be respected (such as retries) will be respected when making this +request. + +```py +import httpx + +response = client.post( + "/foo", + cast_to=httpx.Response, + body={"my_param": True}, +) + +print(response.headers.get("x-foo")) +``` + +#### Undocumented params + +If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request +options. + +#### Undocumented properties + +To access undocumented response properties, you can access the extra fields like `response.unknown_prop`. You +can also get all the extra fields on the Pydantic model as a dict with +[`response.model_extra`](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_extra). + ### Configuring the HTTP client You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: From b9ede81a7b936794160ece4abb9c33e6e41c0285 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 17:36:15 +0000 Subject: [PATCH 23/26] chore(internal): formatting change (#394) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 059f64fd..eaf33af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,6 +130,7 @@ reportImplicitOverride = true reportImportCycles = false reportPrivateUsage = false + [tool.ruff] line-length = 120 output-format = "grouped" From 911d3e24e30e6409e68a44389b58d3be18f7053a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:25:41 +0000 Subject: [PATCH 24/26] feat(api): updates (#395) --- src/lithic/resources/cards/cards.py | 24 +++++++++++++++++++ src/lithic/resources/events/events.py | 8 +++++++ src/lithic/resources/events/subscriptions.py | 24 +++++++++++++++++++ .../external_bank_accounts.py | 10 ++++++++ src/lithic/types/card_provision_params.py | 14 +++++++++++ src/lithic/types/event.py | 4 ++++ src/lithic/types/event_list_params.py | 4 ++++ src/lithic/types/event_subscription.py | 4 ++++ .../events/subscription_create_params.py | 4 ++++ ...scription_send_simulated_example_params.py | 4 ++++ .../events/subscription_update_params.py | 4 ++++ .../external_bank_account_create_params.py | 3 +++ .../external_bank_account_create_response.py | 3 +++ .../external_bank_account_list_response.py | 3 +++ ...external_bank_account_retrieve_response.py | 3 +++ ...k_account_retry_micro_deposits_response.py | 3 +++ .../external_bank_account_update_response.py | 3 +++ .../micro_deposit_create_response.py | 3 +++ src/lithic/types/financial_account.py | 3 +++ .../statements/line_item_list_response.py | 4 ++++ src/lithic/types/financial_transaction.py | 4 ++++ tests/api_resources/test_cards.py | 4 ++++ .../test_external_bank_accounts.py | 18 +++++++------- 23 files changed, 150 insertions(+), 8 deletions(-) diff --git a/src/lithic/resources/cards/cards.py b/src/lithic/resources/cards/cards.py index f7dd79c1..1b9a7675 100644 --- a/src/lithic/resources/cards/cards.py +++ b/src/lithic/resources/cards/cards.py @@ -657,6 +657,8 @@ def provision( card_token: str, *, certificate: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, + client_device_id: str | NotGiven = NOT_GIVEN, + client_wallet_account_id: str | NotGiven = NOT_GIVEN, digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | NotGiven = NOT_GIVEN, nonce: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, nonce_signature: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, @@ -681,6 +683,14 @@ def provision( encoded in PEM format with headers `(-----BEGIN CERTIFICATE-----)` and trailers omitted. Provided by the device's wallet. + client_device_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Stable device identification set by the wallet + provider. + + client_wallet_account_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Consumer ID that identifies the wallet account + holder entity. + digital_wallet: Name of digital wallet provider. nonce: Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only @@ -706,6 +716,8 @@ def provision( body=maybe_transform( { "certificate": certificate, + "client_device_id": client_device_id, + "client_wallet_account_id": client_wallet_account_id, "digital_wallet": digital_wallet, "nonce": nonce, "nonce_signature": nonce_signature, @@ -1521,6 +1533,8 @@ async def provision( card_token: str, *, certificate: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, + client_device_id: str | NotGiven = NOT_GIVEN, + client_wallet_account_id: str | NotGiven = NOT_GIVEN, digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] | NotGiven = NOT_GIVEN, nonce: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, nonce_signature: Union[str, Base64FileInput] | NotGiven = NOT_GIVEN, @@ -1545,6 +1559,14 @@ async def provision( encoded in PEM format with headers `(-----BEGIN CERTIFICATE-----)` and trailers omitted. Provided by the device's wallet. + client_device_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Stable device identification set by the wallet + provider. + + client_wallet_account_id: Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Consumer ID that identifies the wallet account + holder entity. + digital_wallet: Name of digital wallet provider. nonce: Only applicable if `digital_wallet` is `APPLE_PAY`. Omit to receive only @@ -1570,6 +1592,8 @@ async def provision( body=await async_maybe_transform( { "certificate": certificate, + "client_device_id": client_device_id, + "client_wallet_account_id": client_wallet_account_id, "digital_wallet": digital_wallet, "nonce": nonce, "nonce_signature": nonce_signature, diff --git a/src/lithic/resources/events/events.py b/src/lithic/resources/events/events.py index 1c927593..e6e72f44 100644 --- a/src/lithic/resources/events/events.py +++ b/src/lithic/resources/events/events.py @@ -104,8 +104,12 @@ def list( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] @@ -328,8 +332,12 @@ def list( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/resources/events/subscriptions.py b/src/lithic/resources/events/subscriptions.py index 0bb4c706..a2e97f93 100644 --- a/src/lithic/resources/events/subscriptions.py +++ b/src/lithic/resources/events/subscriptions.py @@ -67,8 +67,12 @@ def create( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] @@ -176,8 +180,12 @@ def update( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] @@ -591,8 +599,12 @@ def send_simulated_example( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] @@ -665,8 +677,12 @@ async def create( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] @@ -774,8 +790,12 @@ async def update( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] @@ -1189,8 +1209,12 @@ async def send_simulated_example( "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py index 433604d2..691d9882 100644 --- a/src/lithic/resources/external_bank_accounts/external_bank_accounts.py +++ b/src/lithic/resources/external_bank_accounts/external_bank_accounts.py @@ -78,6 +78,7 @@ def create( company_id: str | NotGiven = NOT_GIVEN, dob: Union[str, date] | NotGiven = NOT_GIVEN, doing_business_as: str | NotGiven = NOT_GIVEN, + financial_account_token: str | NotGiven = NOT_GIVEN, name: str | NotGiven = NOT_GIVEN, user_defined_id: str | NotGiven = NOT_GIVEN, verification_enforcement: bool | NotGiven = NOT_GIVEN, @@ -97,6 +98,8 @@ def create( dob: Date of Birth of the Individual that owns the external bank account + financial_account_token: The financial account token of the operating account used to verify the account + verification_enforcement: Indicates whether verification was enforced for a given association record. For MICRO_DEPOSIT, option to disable verification if the external bank account has already been verified before. By default, verification will be required unless @@ -177,6 +180,7 @@ def create( company_id: str | NotGiven = NOT_GIVEN, dob: Union[str, date] | NotGiven = NOT_GIVEN, doing_business_as: str | NotGiven = NOT_GIVEN, + financial_account_token: str | NotGiven = NOT_GIVEN, name: str | NotGiven = NOT_GIVEN, user_defined_id: str | NotGiven = NOT_GIVEN, verification_enforcement: bool | NotGiven = NOT_GIVEN, @@ -205,6 +209,7 @@ def create( "company_id": company_id, "dob": dob, "doing_business_as": doing_business_as, + "financial_account_token": financial_account_token, "name": name, "user_defined_id": user_defined_id, "verification_enforcement": verification_enforcement, @@ -446,6 +451,7 @@ async def create( company_id: str | NotGiven = NOT_GIVEN, dob: Union[str, date] | NotGiven = NOT_GIVEN, doing_business_as: str | NotGiven = NOT_GIVEN, + financial_account_token: str | NotGiven = NOT_GIVEN, name: str | NotGiven = NOT_GIVEN, user_defined_id: str | NotGiven = NOT_GIVEN, verification_enforcement: bool | NotGiven = NOT_GIVEN, @@ -465,6 +471,8 @@ async def create( dob: Date of Birth of the Individual that owns the external bank account + financial_account_token: The financial account token of the operating account used to verify the account + verification_enforcement: Indicates whether verification was enforced for a given association record. For MICRO_DEPOSIT, option to disable verification if the external bank account has already been verified before. By default, verification will be required unless @@ -545,6 +553,7 @@ async def create( company_id: str | NotGiven = NOT_GIVEN, dob: Union[str, date] | NotGiven = NOT_GIVEN, doing_business_as: str | NotGiven = NOT_GIVEN, + financial_account_token: str | NotGiven = NOT_GIVEN, name: str | NotGiven = NOT_GIVEN, user_defined_id: str | NotGiven = NOT_GIVEN, verification_enforcement: bool | NotGiven = NOT_GIVEN, @@ -573,6 +582,7 @@ async def create( "company_id": company_id, "dob": dob, "doing_business_as": doing_business_as, + "financial_account_token": financial_account_token, "name": name, "user_defined_id": user_defined_id, "verification_enforcement": verification_enforcement, diff --git a/src/lithic/types/card_provision_params.py b/src/lithic/types/card_provision_params.py index b68c9ec7..48ba1e86 100644 --- a/src/lithic/types/card_provision_params.py +++ b/src/lithic/types/card_provision_params.py @@ -21,6 +21,20 @@ class CardProvisionParams(TypedDict, total=False): wallet. """ + client_device_id: str + """ + Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Stable device identification set by the wallet + provider. + """ + + client_wallet_account_id: str + """ + Only applicable if `digital_wallet` is `GOOGLE_PAY` or `SAMSUNG_PAY` and the + card is on the Visa network. Consumer ID that identifies the wallet account + holder entity. + """ + digital_wallet: Literal["APPLE_PAY", "GOOGLE_PAY", "SAMSUNG_PAY"] """Name of digital wallet provider.""" diff --git a/src/lithic/types/event.py b/src/lithic/types/event.py index 7f3c7c2e..83956045 100644 --- a/src/lithic/types/event.py +++ b/src/lithic/types/event.py @@ -33,8 +33,12 @@ class Event(BaseModel): "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/types/event_list_params.py b/src/lithic/types/event_list_params.py index c38315cc..a77e5fb6 100644 --- a/src/lithic/types/event_list_params.py +++ b/src/lithic/types/event_list_params.py @@ -45,8 +45,12 @@ class EventListParams(TypedDict, total=False): "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/types/event_subscription.py b/src/lithic/types/event_subscription.py index c16a0bf0..0ae91e5b 100644 --- a/src/lithic/types/event_subscription.py +++ b/src/lithic/types/event_subscription.py @@ -36,8 +36,12 @@ class EventSubscription(BaseModel): "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/types/events/subscription_create_params.py b/src/lithic/types/events/subscription_create_params.py index 4f3cd23d..4ecf700e 100644 --- a/src/lithic/types/events/subscription_create_params.py +++ b/src/lithic/types/events/subscription_create_params.py @@ -33,8 +33,12 @@ class SubscriptionCreateParams(TypedDict, total=False): "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/types/events/subscription_send_simulated_example_params.py b/src/lithic/types/events/subscription_send_simulated_example_params.py index 0bad121b..8ca00078 100644 --- a/src/lithic/types/events/subscription_send_simulated_example_params.py +++ b/src/lithic/types/events/subscription_send_simulated_example_params.py @@ -22,8 +22,12 @@ class SubscriptionSendSimulatedExampleParams(TypedDict, total=False): "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/types/events/subscription_update_params.py b/src/lithic/types/events/subscription_update_params.py index 4f297f34..49f099aa 100644 --- a/src/lithic/types/events/subscription_update_params.py +++ b/src/lithic/types/events/subscription_update_params.py @@ -33,8 +33,12 @@ class SubscriptionUpdateParams(TypedDict, total=False): "digital_wallet.tokenization_two_factor_authentication_code", "dispute.updated", "dispute_evidence.upload_failed", + "external_bank_account.created", + "external_bank_account.updated", + "financial_account.created", "payment_transaction.created", "payment_transaction.updated", + "statements.created", "three_ds_authentication.created", "transfer_transaction.created", ] diff --git a/src/lithic/types/external_bank_account_create_params.py b/src/lithic/types/external_bank_account_create_params.py index 161546fb..d8f79ba2 100644 --- a/src/lithic/types/external_bank_account_create_params.py +++ b/src/lithic/types/external_bank_account_create_params.py @@ -50,6 +50,9 @@ class BankVerifiedCreateBankAccountAPIRequest(TypedDict, total=False): doing_business_as: str + financial_account_token: str + """The financial account token of the operating account used to verify the account""" + name: str user_defined_id: str diff --git a/src/lithic/types/external_bank_account_create_response.py b/src/lithic/types/external_bank_account_create_response.py index 38315f71..aca4a05b 100644 --- a/src/lithic/types/external_bank_account_create_response.py +++ b/src/lithic/types/external_bank_account_create_response.py @@ -82,6 +82,9 @@ class ExternalBankAccountCreateResponse(BaseModel): doing_business_as: Optional[str] = None + financial_account_token: Optional[str] = None + """The financial account token of the operating account used to verify the account""" + name: Optional[str] = None """The nickname given to this record of External Bank Account""" diff --git a/src/lithic/types/external_bank_account_list_response.py b/src/lithic/types/external_bank_account_list_response.py index 78d0082d..8ff7611d 100644 --- a/src/lithic/types/external_bank_account_list_response.py +++ b/src/lithic/types/external_bank_account_list_response.py @@ -82,6 +82,9 @@ class ExternalBankAccountListResponse(BaseModel): doing_business_as: Optional[str] = None + financial_account_token: Optional[str] = None + """The financial account token of the operating account used to verify the account""" + name: Optional[str] = None """The nickname given to this record of External Bank Account""" diff --git a/src/lithic/types/external_bank_account_retrieve_response.py b/src/lithic/types/external_bank_account_retrieve_response.py index 3c2aa5af..bf239847 100644 --- a/src/lithic/types/external_bank_account_retrieve_response.py +++ b/src/lithic/types/external_bank_account_retrieve_response.py @@ -82,6 +82,9 @@ class ExternalBankAccountRetrieveResponse(BaseModel): doing_business_as: Optional[str] = None + financial_account_token: Optional[str] = None + """The financial account token of the operating account used to verify the account""" + name: Optional[str] = None """The nickname given to this record of External Bank Account""" diff --git a/src/lithic/types/external_bank_account_retry_micro_deposits_response.py b/src/lithic/types/external_bank_account_retry_micro_deposits_response.py index 1bd7bef4..f698c301 100644 --- a/src/lithic/types/external_bank_account_retry_micro_deposits_response.py +++ b/src/lithic/types/external_bank_account_retry_micro_deposits_response.py @@ -82,6 +82,9 @@ class ExternalBankAccountRetryMicroDepositsResponse(BaseModel): doing_business_as: Optional[str] = None + financial_account_token: Optional[str] = None + """The financial account token of the operating account used to verify the account""" + name: Optional[str] = None """The nickname given to this record of External Bank Account""" diff --git a/src/lithic/types/external_bank_account_update_response.py b/src/lithic/types/external_bank_account_update_response.py index a0900ac8..d845289c 100644 --- a/src/lithic/types/external_bank_account_update_response.py +++ b/src/lithic/types/external_bank_account_update_response.py @@ -82,6 +82,9 @@ class ExternalBankAccountUpdateResponse(BaseModel): doing_business_as: Optional[str] = None + financial_account_token: Optional[str] = None + """The financial account token of the operating account used to verify the account""" + name: Optional[str] = None """The nickname given to this record of External Bank Account""" diff --git a/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py b/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py index 2b2f73d4..fc2f8766 100644 --- a/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py +++ b/src/lithic/types/external_bank_accounts/micro_deposit_create_response.py @@ -82,6 +82,9 @@ class MicroDepositCreateResponse(BaseModel): doing_business_as: Optional[str] = None + financial_account_token: Optional[str] = None + """The financial account token of the operating account used to verify the account""" + name: Optional[str] = None """The nickname given to this record of External Bank Account""" diff --git a/src/lithic/types/financial_account.py b/src/lithic/types/financial_account.py index 6a4a539e..1ff945e7 100644 --- a/src/lithic/types/financial_account.py +++ b/src/lithic/types/financial_account.py @@ -25,6 +25,9 @@ class FinancialAccount(BaseModel): account_number: Optional[str] = None """Account number for your Lithic-assigned bank account number, if applicable.""" + account_token: Optional[str] = None + """Account token of the financial account if applicable.""" + nickname: Optional[str] = None """User-defined nickname for the financial account.""" diff --git a/src/lithic/types/financial_accounts/statements/line_item_list_response.py b/src/lithic/types/financial_accounts/statements/line_item_list_response.py index 62f7c5d8..06ccddbf 100644 --- a/src/lithic/types/financial_accounts/statements/line_item_list_response.py +++ b/src/lithic/types/financial_accounts/statements/line_item_list_response.py @@ -24,12 +24,16 @@ class LineItemListResponse(BaseModel): """3-digit alphabetic ISO 4217 code for the settling currency of the transaction""" event_type: Literal[ + "ACH_EXCEEDED_THRESHOLD", "ACH_INSUFFICIENT_FUNDS", + "ACH_INVALID_ACCOUNT", "ACH_ORIGINATION_PENDING", + "ACH_ORIGINATION_PROCESSED", "ACH_ORIGINATION_RELEASED", "ACH_RECEIPT_PENDING", "ACH_RECEIPT_RELEASED", "ACH_RETURN", + "ACH_RETURN_PENDING", "AUTHORIZATION", "AUTHORIZATION_ADVICE", "AUTHORIZATION_EXPIRY", diff --git a/src/lithic/types/financial_transaction.py b/src/lithic/types/financial_transaction.py index 72bd5548..18af7f9e 100644 --- a/src/lithic/types/financial_transaction.py +++ b/src/lithic/types/financial_transaction.py @@ -30,12 +30,16 @@ class Event(BaseModel): type: Optional[ Literal[ + "ACH_EXCEEDED_THRESHOLD", "ACH_INSUFFICIENT_FUNDS", + "ACH_INVALID_ACCOUNT", "ACH_ORIGINATION_PENDING", + "ACH_ORIGINATION_PROCESSED", "ACH_ORIGINATION_RELEASED", "ACH_RECEIPT_PENDING", "ACH_RECEIPT_RELEASED", "ACH_RETURN", + "ACH_RETURN_PENDING", "AUTHORIZATION", "AUTHORIZATION_ADVICE", "AUTHORIZATION_EXPIRY", diff --git a/tests/api_resources/test_cards.py b/tests/api_resources/test_cards.py index 56359153..ae8bdb02 100644 --- a/tests/api_resources/test_cards.py +++ b/tests/api_resources/test_cards.py @@ -274,6 +274,8 @@ def test_method_provision_with_all_params(self, client: Lithic) -> None: card = client.cards.provision( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", certificate="U3RhaW5sZXNzIHJvY2tz", + client_device_id="string", + client_wallet_account_id="string", digital_wallet="GOOGLE_PAY", nonce="U3RhaW5sZXNzIHJvY2tz", nonce_signature="U3RhaW5sZXNzIHJvY2tz", @@ -795,6 +797,8 @@ async def test_method_provision_with_all_params(self, async_client: AsyncLithic) card = await async_client.cards.provision( "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", certificate="U3RhaW5sZXNzIHJvY2tz", + client_device_id="string", + client_wallet_account_id="string", digital_wallet="GOOGLE_PAY", nonce="U3RhaW5sZXNzIHJvY2tz", nonce_signature="U3RhaW5sZXNzIHJvY2tz", diff --git a/tests/api_resources/test_external_bank_accounts.py b/tests/api_resources/test_external_bank_accounts.py index d76a2794..f2ed01e7 100644 --- a/tests/api_resources/test_external_bank_accounts.py +++ b/tests/api_resources/test_external_bank_accounts.py @@ -28,7 +28,7 @@ class TestExternalBankAccounts: @parametrize def test_method_create_overload_1(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -42,7 +42,7 @@ def test_method_create_overload_1(self, client: Lithic) -> None: @parametrize def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: external_bank_account = client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -62,6 +62,7 @@ def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: company_id="x", dob=parse_date("2019-12-27"), doing_business_as="string", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="x", user_defined_id="string", verification_enforcement=True, @@ -71,7 +72,7 @@ def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: @parametrize def test_raw_response_create_overload_1(self, client: Lithic) -> None: response = client.external_bank_accounts.with_raw_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -89,7 +90,7 @@ def test_raw_response_create_overload_1(self, client: Lithic) -> None: @parametrize def test_streaming_response_create_overload_1(self, client: Lithic) -> None: with client.external_bank_accounts.with_streaming_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -352,7 +353,7 @@ class TestAsyncExternalBankAccounts: @parametrize async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -366,7 +367,7 @@ async def test_method_create_overload_1(self, async_client: AsyncLithic) -> None @parametrize async def test_method_create_with_all_params_overload_1(self, async_client: AsyncLithic) -> None: external_bank_account = await async_client.external_bank_accounts.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -386,6 +387,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn company_id="x", dob=parse_date("2019-12-27"), doing_business_as="string", + financial_account_token="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", name="x", user_defined_id="string", verification_enforcement=True, @@ -395,7 +397,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn @parametrize async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) -> None: response = await async_client.external_bank_accounts.with_raw_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", @@ -413,7 +415,7 @@ async def test_raw_response_create_overload_1(self, async_client: AsyncLithic) - @parametrize async def test_streaming_response_create_overload_1(self, async_client: AsyncLithic) -> None: async with async_client.external_bank_accounts.with_streaming_response.create( - account_number="string", + account_number="12345678901234567", country="USD", currency="USD", owner="x", From ce9d732ec90e776b5eea0e77bc880d662921d639 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:15:09 +0000 Subject: [PATCH 25/26] feat(api): adds closed state (#396) --- src/lithic/types/account.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lithic/types/account.py b/src/lithic/types/account.py index fdab1999..763934ff 100644 --- a/src/lithic/types/account.py +++ b/src/lithic/types/account.py @@ -82,12 +82,14 @@ class Account(BaseModel): feature is disabled. """ - state: Literal["ACTIVE", "PAUSED"] + state: Literal["ACTIVE", "PAUSED", "CLOSED"] """Account state: - `ACTIVE` - Account is able to transact and create new cards. - `PAUSED` - Account will not be able to transact or create new cards. It can be - set back to `ACTIVE`. + set back to `ACTIVE`. `CLOSED` - Account will not be able to transact or + create new cards. `CLOSED` cards are also unable to be transitioned to + `ACTIVE` or `PAUSED` states. """ account_holder: Optional[AccountHolder] = None From 7e974d83210a825ae99f4dae8ecaf06bfa3905d3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 20:15:31 +0000 Subject: [PATCH 26/26] release: 0.40.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 44 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/lithic/_version.py | 2 +- 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1b5dc400..0a40b9d7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.39.0" + ".": "0.40.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f75152d6..c3008abd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 0.40.0 (2024-03-21) + +Full Changelog: [v0.39.0...v0.40.0](https://github.com/lithic-com/lithic-python/compare/v0.39.0...v0.40.0) + +### Features + +* **api:** adds closed state ([#396](https://github.com/lithic-com/lithic-python/issues/396)) ([ce9d732](https://github.com/lithic-com/lithic-python/commit/ce9d732ec90e776b5eea0e77bc880d662921d639)) +* **api:** updates ([#395](https://github.com/lithic-com/lithic-python/issues/395)) ([911d3e2](https://github.com/lithic-com/lithic-python/commit/911d3e24e30e6409e68a44389b58d3be18f7053a)) + + +### Performance Improvements + +* cache TypeAdapters ([#389](https://github.com/lithic-com/lithic-python/issues/389)) ([16f35fa](https://github.com/lithic-com/lithic-python/commit/16f35fad0bc1f250028991635f39ee34c29c450a)) + + +### Chores + +* add back examples ([e0eba72](https://github.com/lithic-com/lithic-python/commit/e0eba7230c6256a2b90ea1de7a9e814f80da4398)) +* add back removed code ([36c5c4e](https://github.com/lithic-com/lithic-python/commit/36c5c4e84ee61ca9b9cae51d95c7e9bb1906eec2)) +* **client:** improve error message for invalid http_client argument ([#380](https://github.com/lithic-com/lithic-python/issues/380)) ([0ea3801](https://github.com/lithic-com/lithic-python/commit/0ea38014e414fd60fd461992b52743ce8499ba66)) +* **docs:** add back custom readme code ([23a52b2](https://github.com/lithic-com/lithic-python/commit/23a52b28bcab6293ff4b3a212dbc389026c62a08)) +* **docs:** mention install from git repo ([#377](https://github.com/lithic-com/lithic-python/issues/377)) ([8b6e48c](https://github.com/lithic-com/lithic-python/commit/8b6e48c5f72df0f8380dbde642ccb31c809c9e30)) +* **docs:** temporarily remove custom readme code ([#387](https://github.com/lithic-com/lithic-python/issues/387)) ([93530fd](https://github.com/lithic-com/lithic-python/commit/93530fdc4b004c6ec353a550b9641e37a898c1ef)) +* export NOT_GIVEN sentinel value ([#384](https://github.com/lithic-com/lithic-python/issues/384)) ([841497f](https://github.com/lithic-com/lithic-python/commit/841497faed8a1b22adf6a724eb5b7e936276974e)) +* **internal:** add core support for deserializing into number response ([#381](https://github.com/lithic-com/lithic-python/issues/381)) ([cb5e9d2](https://github.com/lithic-com/lithic-python/commit/cb5e9d27a6bacf861921e49b30cc1abee365530a)) +* **internal:** bump pyright ([#382](https://github.com/lithic-com/lithic-python/issues/382)) ([8aad846](https://github.com/lithic-com/lithic-python/commit/8aad846ffad52816d6ee55ca64aaf3b92649998d)) +* **internal:** formatting change ([#394](https://github.com/lithic-com/lithic-python/issues/394)) ([b9ede81](https://github.com/lithic-com/lithic-python/commit/b9ede81a7b936794160ece4abb9c33e6e41c0285)) +* **internal:** improve deserialisation of discriminated unions ([#385](https://github.com/lithic-com/lithic-python/issues/385)) ([9062866](https://github.com/lithic-com/lithic-python/commit/9062866fa7aa87f9f5c25c401638415ea4986a09)) +* **internal:** loosen input type for util function ([#392](https://github.com/lithic-com/lithic-python/issues/392)) ([1294d5b](https://github.com/lithic-com/lithic-python/commit/1294d5b6766a15dd5956395324dd38ded94adcb0)) +* **internal:** minor core client restructuring ([#374](https://github.com/lithic-com/lithic-python/issues/374)) ([bca5b1c](https://github.com/lithic-com/lithic-python/commit/bca5b1c9b5f65743883ca48b54de22bffd7c6560)) +* **internal:** split up transforms into sync / async ([#378](https://github.com/lithic-com/lithic-python/issues/378)) ([0cecc63](https://github.com/lithic-com/lithic-python/commit/0cecc6324700a8d1b38222ff032b5f492a0e663b)) +* **internal:** support more input types ([#379](https://github.com/lithic-com/lithic-python/issues/379)) ([0cde41d](https://github.com/lithic-com/lithic-python/commit/0cde41d7d8850d1d6bca08fdc1358241d62779de)) +* **internal:** support parsing Annotated types ([#383](https://github.com/lithic-com/lithic-python/issues/383)) ([ded4ec2](https://github.com/lithic-com/lithic-python/commit/ded4ec2ef0ea794238b1658a989701b24d53d465)) +* **internal:** update generated pragma comment ([#391](https://github.com/lithic-com/lithic-python/issues/391)) ([cac2a8b](https://github.com/lithic-com/lithic-python/commit/cac2a8b1319904b4d07a7864d426284ebd557d63)) +* temporarily remove examples for migration ([644a601](https://github.com/lithic-com/lithic-python/commit/644a601cf2c271e5e3c3aff0a5084f14b0e1fb74)) +* temporarily remove various code as part of refactor ([#388](https://github.com/lithic-com/lithic-python/issues/388)) ([306df73](https://github.com/lithic-com/lithic-python/commit/306df733bf153bf89931060cf2c5d9aa170c86ed)) + + +### Documentation + +* **contributing:** improve wording ([#376](https://github.com/lithic-com/lithic-python/issues/376)) ([ea83617](https://github.com/lithic-com/lithic-python/commit/ea836172c1533eea4008732215b72d3f8ef0065d)) +* fix typo in CONTRIBUTING.md ([#390](https://github.com/lithic-com/lithic-python/issues/390)) ([2fc74c2](https://github.com/lithic-com/lithic-python/commit/2fc74c260e3166ed92c7613f6952604b253d6607)) +* **readme:** document how to make undocumented requests ([#393](https://github.com/lithic-com/lithic-python/issues/393)) ([0fb722f](https://github.com/lithic-com/lithic-python/commit/0fb722fe212c67cdcab814ba68158c74607c94f6)) + ## 0.39.0 (2024-02-27) Full Changelog: [v0.38.0...v0.39.0](https://github.com/lithic-com/lithic-python/compare/v0.38.0...v0.39.0) diff --git a/pyproject.toml b/pyproject.toml index eaf33af6..ef45eb46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lithic" -version = "0.39.0" +version = "0.40.0" description = "The official Python library for the lithic API" readme = "README.md" license = "Apache-2.0" diff --git a/src/lithic/_version.py b/src/lithic/_version.py index 57b356e4..6a897812 100644 --- a/src/lithic/_version.py +++ b/src/lithic/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "lithic" -__version__ = "0.39.0" # x-release-please-version +__version__ = "0.40.0" # x-release-please-version