diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d61779f3..d3dc9f51 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.114.0" + ".": "0.115.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index d9f35dd3..d968c15f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 175 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-ce2adff9b644ed4562b5342a4a43d0b40c98d43b4e063b4626f4ca5d342f1b92.yml -openapi_spec_hash: fbc84b866ce96457261ac58b4e75c71d -config_hash: 31d71922d7838f34ae0875c9b8026d99 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-05d665e9c7e81d85c0d6629bf3898e2eb221268d677b772ef3da3891b25d8346.yml +openapi_spec_hash: fd8900412ab9a393719dba3669d44d3a +config_hash: faacaff68ffb3a4d051f0a7b8442e099 diff --git a/CHANGELOG.md b/CHANGELOG.md index f5f83006..0633e74e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.115.0 (2026-01-30) + +Full Changelog: [v0.114.0...v0.115.0](https://github.com/lithic-com/lithic-python/compare/v0.114.0...v0.115.0) + +### Features + +* **api:** Add naics_code to account holder requests/responses ([a7135fb](https://github.com/lithic-com/lithic-python/commit/a7135fb5f63687973cc1856731eb6c7f524994e3)) +* **client:** add custom JSON encoder for extended type support ([5658dcc](https://github.com/lithic-com/lithic-python/commit/5658dccf4d989063d2d4ffdb243cfec17861a000)) + + +### Chores + +* configure new SDK language ([292317d](https://github.com/lithic-com/lithic-python/commit/292317d003f007992d8848fbe4a870164bac9d29)) +* Enable stainless MCP in config ([a799eec](https://github.com/lithic-com/lithic-python/commit/a799eeca71933e98243cf351caf374b9a59d9ce9)) + ## 0.114.0 (2026-01-27) Full Changelog: [v0.113.0...v0.114.0](https://github.com/lithic-com/lithic-python/compare/v0.113.0...v0.114.0) diff --git a/README.md b/README.md index 0aaa73df..d91fcf39 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,15 @@ The Lithic Python library provides convenient access to the Lithic REST API from application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). +## MCP Server + +Use the Lithic MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=lithic-mcp&config=eyJuYW1lIjoibGl0aGljLW1jcCIsInRyYW5zcG9ydCI6Imh0dHAiLCJ1cmwiOiJodHRwczovL2xpdGhpYy5zdGxtY3AuY29tIiwiaGVhZGVycyI6eyJ4LWxpdGhpYy1hcGkta2V5IjoiTXkgTGl0aGljIEFQSSBLZXkifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22lithic-mcp%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Flithic.stlmcp.com%22%2C%22headers%22%3A%7B%22x-lithic-api-key%22%3A%22My%20Lithic%20API%20Key%22%7D%7D) + +> Note: You may need to set environment variables in your MCP client. + ## Documentation The REST API documentation can be found on [docs.lithic.com](https://docs.lithic.com). The full API of this library can be found in [api.md](api.md). diff --git a/pyproject.toml b/pyproject.toml index 4c357454..37ed227d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lithic" -version = "0.114.0" +version = "0.115.0" description = "The official Python library for the lithic API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/lithic/_base_client.py b/src/lithic/_base_client.py index 73cedbe6..e992a795 100644 --- a/src/lithic/_base_client.py +++ b/src/lithic/_base_client.py @@ -86,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps from ._legacy_response import LegacyAPIResponse log: logging.Logger = logging.getLogger(__name__) @@ -555,8 +556,10 @@ def _build_request( kwargs["content"] = options.content elif isinstance(json_data, bytes): kwargs["content"] = json_data - else: - kwargs["json"] = json_data if is_given(json_data) else None + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/lithic/_compat.py b/src/lithic/_compat.py index bdef67f0..786ff42a 100644 --- a/src/lithic/_compat.py +++ b/src/lithic/_compat.py @@ -139,6 +139,7 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( @@ -148,13 +149,12 @@ def model_dump( exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) diff --git a/src/lithic/_utils/_json.py b/src/lithic/_utils/_json.py new file mode 100644 index 00000000..60584214 --- /dev/null +++ b/src/lithic/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/lithic/_version.py b/src/lithic/_version.py index 9e8952a6..9b480c33 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.114.0" # x-release-please-version +__version__ = "0.115.0" # x-release-please-version diff --git a/src/lithic/resources/account_holders.py b/src/lithic/resources/account_holders.py index bd371f9b..09a7cd11 100644 --- a/src/lithic/resources/account_holders.py +++ b/src/lithic/resources/account_holders.py @@ -70,6 +70,7 @@ def create( beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, external_id: str | Omit = omit, kyb_passed_timestamp: str | Omit = omit, + naics_code: str | Omit = omit, website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -127,6 +128,9 @@ def create( This field is required only if workflow type is `KYB_BYO`. + naics_code: 6-digit North American Industry Classification System (NAICS) code for the + business. + website_url: Company website URL. extra_headers: Send extra headers @@ -148,6 +152,7 @@ def create( | Omit = omit, control_person: account_holder_create_params.KYBDelegatedControlPerson | Omit = omit, external_id: str | Omit = omit, + naics_code: str | Omit = omit, nature_of_business: str | Omit = omit, tos_timestamp: str | Omit = omit, website_url: str | Omit = omit, @@ -191,6 +196,9 @@ def create( external_id: A user provided id that can be used to link an account holder with an external system + naics_code: 6-digit North American Industry Classification System (NAICS) code for the + business. + nature_of_business: Short description of the company's line of business (i.e., what does the company do?). @@ -364,6 +372,7 @@ def create( beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, external_id: str | Omit = omit, kyb_passed_timestamp: str | Omit = omit, + naics_code: str | Omit = omit, website_url: str | Omit = omit, individual: account_holder_create_params.KYCIndividual | Omit = omit, kyc_passed_timestamp: str | Omit = omit, @@ -396,6 +405,7 @@ def create( "beneficial_owner_entities": beneficial_owner_entities, "external_id": external_id, "kyb_passed_timestamp": kyb_passed_timestamp, + "naics_code": naics_code, "website_url": website_url, "individual": individual, "kyc_passed_timestamp": kyc_passed_timestamp, @@ -463,6 +473,7 @@ def update( business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, external_id: str | Omit = omit, + naics_code: str | Omit = omit, nature_of_business: str | Omit = omit, website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -509,6 +520,9 @@ def update( external_id: A user provided id that can be used to link an account holder with an external system + naics_code: 6-digit North American Industry Classification System (NAICS) code for the + business. + nature_of_business: Short description of the company's line of business (i.e., what does the company do?). @@ -639,6 +653,7 @@ def update( business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, external_id: str | Omit = omit, + naics_code: str | Omit = omit, nature_of_business: str | Omit = omit, website_url: str | Omit = omit, individual: account_holder_update_params.KYCPatchRequestIndividual | Omit = omit, @@ -671,6 +686,7 @@ def update( "business_entity": business_entity, "control_person": control_person, "external_id": external_id, + "naics_code": naics_code, "nature_of_business": nature_of_business, "website_url": website_url, "individual": individual, @@ -1140,6 +1156,7 @@ async def create( beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, external_id: str | Omit = omit, kyb_passed_timestamp: str | Omit = omit, + naics_code: str | Omit = omit, website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -1197,6 +1214,9 @@ async def create( This field is required only if workflow type is `KYB_BYO`. + naics_code: 6-digit North American Industry Classification System (NAICS) code for the + business. + website_url: Company website URL. extra_headers: Send extra headers @@ -1218,6 +1238,7 @@ async def create( | Omit = omit, control_person: account_holder_create_params.KYBDelegatedControlPerson | Omit = omit, external_id: str | Omit = omit, + naics_code: str | Omit = omit, nature_of_business: str | Omit = omit, tos_timestamp: str | Omit = omit, website_url: str | Omit = omit, @@ -1261,6 +1282,9 @@ async def create( external_id: A user provided id that can be used to link an account holder with an external system + naics_code: 6-digit North American Industry Classification System (NAICS) code for the + business. + nature_of_business: Short description of the company's line of business (i.e., what does the company do?). @@ -1434,6 +1458,7 @@ async def create( beneficial_owner_entities: Iterable[account_holder_create_params.KYBBeneficialOwnerEntity] | Omit = omit, external_id: str | Omit = omit, kyb_passed_timestamp: str | Omit = omit, + naics_code: str | Omit = omit, website_url: str | Omit = omit, individual: account_holder_create_params.KYCIndividual | Omit = omit, kyc_passed_timestamp: str | Omit = omit, @@ -1466,6 +1491,7 @@ async def create( "beneficial_owner_entities": beneficial_owner_entities, "external_id": external_id, "kyb_passed_timestamp": kyb_passed_timestamp, + "naics_code": naics_code, "website_url": website_url, "individual": individual, "kyc_passed_timestamp": kyc_passed_timestamp, @@ -1533,6 +1559,7 @@ async def update( business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, external_id: str | Omit = omit, + naics_code: str | Omit = omit, nature_of_business: str | Omit = omit, website_url: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1579,6 +1606,9 @@ async def update( external_id: A user provided id that can be used to link an account holder with an external system + naics_code: 6-digit North American Industry Classification System (NAICS) code for the + business. + nature_of_business: Short description of the company's line of business (i.e., what does the company do?). @@ -1709,6 +1739,7 @@ async def update( business_entity: account_holder_update_params.KYBPatchRequestBusinessEntity | Omit = omit, control_person: account_holder_update_params.KYBPatchRequestControlPerson | Omit = omit, external_id: str | Omit = omit, + naics_code: str | Omit = omit, nature_of_business: str | Omit = omit, website_url: str | Omit = omit, individual: account_holder_update_params.KYCPatchRequestIndividual | Omit = omit, @@ -1741,6 +1772,7 @@ async def update( "business_entity": business_entity, "control_person": control_person, "external_id": external_id, + "naics_code": naics_code, "nature_of_business": nature_of_business, "website_url": website_url, "individual": individual, diff --git a/src/lithic/types/account_holder.py b/src/lithic/types/account_holder.py index dbe28588..bf384966 100644 --- a/src/lithic/types/account_holder.py +++ b/src/lithic/types/account_holder.py @@ -290,6 +290,13 @@ class AccountHolder(BaseModel): is being run. """ + naics_code: Optional[str] = None + """Only present when user_type == "BUSINESS". + + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + nature_of_business: Optional[str] = None """Only present when user_type == "BUSINESS". @@ -347,8 +354,8 @@ class AccountHolder(BaseModel): If the type is "INDIVIDUAL", the "individual" attribute will be present. If the type is "BUSINESS" then the "business_entity", "control_person", - "beneficial_owner_individuals", "nature_of_business", and "website_url" - attributes will be present. + "beneficial_owner_individuals", "naics_code", "nature_of_business", and + "website_url" attributes will be present. """ verification_application: Optional[VerificationApplication] = None diff --git a/src/lithic/types/account_holder_create_params.py b/src/lithic/types/account_holder_create_params.py index c90cfad2..2c0a27cb 100644 --- a/src/lithic/types/account_holder_create_params.py +++ b/src/lithic/types/account_holder_create_params.py @@ -87,6 +87,12 @@ class KYB(TypedDict, total=False): This field is required only if workflow type is `KYB_BYO`. """ + naics_code: str + """ + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + website_url: str """Company website URL.""" @@ -270,6 +276,12 @@ class KYBDelegated(TypedDict, total=False): system """ + naics_code: str + """ + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + nature_of_business: str """ Short description of the company's line of business (i.e., what does the company diff --git a/src/lithic/types/account_holder_simulate_enrollment_review_response.py b/src/lithic/types/account_holder_simulate_enrollment_review_response.py index 56f53d0d..b2785883 100644 --- a/src/lithic/types/account_holder_simulate_enrollment_review_response.py +++ b/src/lithic/types/account_holder_simulate_enrollment_review_response.py @@ -355,6 +355,13 @@ class AccountHolderSimulateEnrollmentReviewResponse(BaseModel): is being run. """ + naics_code: Optional[str] = None + """Only present when user_type == "BUSINESS". + + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + nature_of_business: Optional[str] = None """Only present when user_type == "BUSINESS". @@ -425,8 +432,8 @@ class AccountHolderSimulateEnrollmentReviewResponse(BaseModel): If the type is "INDIVIDUAL", the "individual" attribute will be present. If the type is "BUSINESS" then the "business_entity", "control_person", - "beneficial_owner_individuals", "nature_of_business", and "website_url" - attributes will be present. + "beneficial_owner_individuals", "naics_code", "nature_of_business", and + "website_url" attributes will be present. """ verification_application: Optional[VerificationApplication] = None diff --git a/src/lithic/types/account_holder_update_params.py b/src/lithic/types/account_holder_update_params.py index ed23ca4a..2551e6c4 100644 --- a/src/lithic/types/account_holder_update_params.py +++ b/src/lithic/types/account_holder_update_params.py @@ -59,6 +59,12 @@ class KYBPatchRequest(TypedDict, total=False): system """ + naics_code: str + """ + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + nature_of_business: str """ Short description of the company's line of business (i.e., what does the company diff --git a/src/lithic/types/account_holder_update_response.py b/src/lithic/types/account_holder_update_response.py index 7c8a1340..304b81de 100644 --- a/src/lithic/types/account_holder_update_response.py +++ b/src/lithic/types/account_holder_update_response.py @@ -358,6 +358,13 @@ class KYBKYCPatchResponse(BaseModel): is being run. """ + naics_code: Optional[str] = None + """Only present when user_type == "BUSINESS". + + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + nature_of_business: Optional[str] = None """Only present when user_type == "BUSINESS". @@ -428,8 +435,8 @@ class KYBKYCPatchResponse(BaseModel): If the type is "INDIVIDUAL", the "individual" attribute will be present. If the type is "BUSINESS" then the "business_entity", "control_person", - "beneficial_owner_individuals", "nature_of_business", and "website_url" - attributes will be present. + "beneficial_owner_individuals", "naics_code", "nature_of_business", and + "website_url" attributes will be present. """ verification_application: Optional[KYBKYCPatchResponseVerificationApplication] = None diff --git a/src/lithic/types/account_holder_updated_webhook_event.py b/src/lithic/types/account_holder_updated_webhook_event.py index fcda2c62..34bfa0fc 100644 --- a/src/lithic/types/account_holder_updated_webhook_event.py +++ b/src/lithic/types/account_holder_updated_webhook_event.py @@ -206,6 +206,12 @@ class KYBPayload(BaseModel): system """ + naics_code: Optional[str] = None + """ + 6-digit North American Industry Classification System (NAICS) code for the + business. Only present if naics_code was included in the update request. + """ + nature_of_business: Optional[str] = None """ Short description of the company's line of business (i.e., what does the company diff --git a/src/lithic/types/kyb_param.py b/src/lithic/types/kyb_param.py index b3131cd4..b02c8749 100644 --- a/src/lithic/types/kyb_param.py +++ b/src/lithic/types/kyb_param.py @@ -220,5 +220,11 @@ class KYBParam(TypedDict, total=False): This field is required only if workflow type is `KYB_BYO`. """ + naics_code: str + """ + 6-digit North American Industry Classification System (NAICS) code for the + business. + """ + website_url: str """Company website URL.""" diff --git a/src/lithic/types/parsed_webhook_event.py b/src/lithic/types/parsed_webhook_event.py index add8c01e..a9db122e 100644 --- a/src/lithic/types/parsed_webhook_event.py +++ b/src/lithic/types/parsed_webhook_event.py @@ -271,6 +271,12 @@ class KYBPayload(BaseModel): system """ + naics_code: Optional[str] = None + """ + 6-digit North American Industry Classification System (NAICS) code for the + business. Only present if naics_code was included in the update request. + """ + nature_of_business: Optional[str] = None """ Short description of the company's line of business (i.e., what does the company diff --git a/tests/api_resources/test_account_holders.py b/tests/api_resources/test_account_holders.py index 3c723a7d..a01b2b4d 100644 --- a/tests/api_resources/test_account_holders.py +++ b/tests/api_resources/test_account_holders.py @@ -151,6 +151,7 @@ def test_method_create_with_all_params_overload_1(self, client: Lithic) -> None: ], external_id="external_id", kyb_passed_timestamp="2018-05-29T21:16:05Z", + naics_code="541512", website_url="www.mybusiness.com", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @@ -336,6 +337,7 @@ def test_method_create_with_all_params_overload_2(self, client: Lithic) -> None: "phone_number": "+15555555555", }, external_id="external_id", + naics_code="541512", nature_of_business="Software company selling solutions to the restaurant industry", tos_timestamp="2022-03-08 08:00:00", website_url="www.mybusiness.com", @@ -699,6 +701,7 @@ def test_method_update_with_all_params_overload_1(self, client: Lithic) -> None: "phone_number": "+15555555555", }, external_id="external_id", + naics_code="541512", nature_of_business="Software company selling solutions to the restaurant industry", website_url="www.mybusiness.com", ) @@ -1240,6 +1243,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn ], external_id="external_id", kyb_passed_timestamp="2018-05-29T21:16:05Z", + naics_code="541512", website_url="www.mybusiness.com", ) assert_matches_type(AccountHolderCreateResponse, account_holder, path=["response"]) @@ -1425,6 +1429,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn "phone_number": "+15555555555", }, external_id="external_id", + naics_code="541512", nature_of_business="Software company selling solutions to the restaurant industry", tos_timestamp="2022-03-08 08:00:00", website_url="www.mybusiness.com", @@ -1788,6 +1793,7 @@ async def test_method_update_with_all_params_overload_1(self, async_client: Asyn "phone_number": "+15555555555", }, external_id="external_id", + naics_code="541512", nature_of_business="Software company selling solutions to the restaurant industry", website_url="www.mybusiness.com", ) diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 00000000..15e48fb1 --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from lithic import _compat +from lithic._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'