Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.113.0"
".": "0.114.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 175
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-4ecc06edca2cfad4eaf11573611e89823fda5f56370bac5cd02a498a6b277d09.yml
openapi_spec_hash: 8f4a30bec4348cbde85b1e65bef9189a
config_hash: 9dddee5f7af579864599849cb28a0770
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/lithic%2Flithic-64e9dd6979ae45f1bb6e77b40e8244ee46673332112ebf33fe2f3a287467ce85.yml
openapi_spec_hash: ce885445b66e95c5671ee72c01882d79
config_hash: 07f0e0f3036a4a5825cee527bc46b0b6
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.114.0 (2026-01-13)

Full Changelog: [v0.113.0...v0.114.0](https://github.com/lithic-com/lithic-python/compare/v0.113.0...v0.114.0)

### Features

* **api:** make filter optional for Spend Velocity Auth Rules ([260d4a6](https://github.com/lithic-com/lithic-python/commit/260d4a653a2c325bbce7bf24474043a3e0974c28))
* **client:** add support for binary request streaming ([941a2e3](https://github.com/lithic-com/lithic-python/commit/941a2e3857a34b9555d9b5ce83149ec2ab36d0c3))


### Bug Fixes

* **api:** Correct field name from ach_hold__period to ach_hold_period ([2b4f00b](https://github.com/lithic-com/lithic-python/commit/2b4f00b6e98ac08aa2e545940ade5abc8f2681b9))
* **api:** rename WIRE_DRAWDOWN_REQUEST to WIRE_INBOUND_DRAWDOWN_REQUEST ([260d4a6](https://github.com/lithic-com/lithic-python/commit/260d4a653a2c325bbce7bf24474043a3e0974c28))


### Chores

* configure new SDK language ([427958f](https://github.com/lithic-com/lithic-python/commit/427958fe0fca3e9162da20580087cc4dcca80b1c))

## 0.113.0 (2026-01-08)

Full Changelog: [v0.112.0...v0.113.0](https://github.com/lithic-com/lithic-python/compare/v0.112.0...v0.113.0)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lithic"
version = "0.113.0"
version = "0.114.0"
description = "The official Python library for the lithic API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
145 changes: 134 additions & 11 deletions src/lithic/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import inspect
import logging
import platform
import warnings
import email.utils
from types import TracebackType
from random import random
Expand Down Expand Up @@ -51,9 +52,11 @@
ResponseT,
AnyMapping,
PostParser,
BinaryTypes,
RequestFiles,
HttpxSendArgs,
RequestOptions,
AsyncBinaryTypes,
HttpxRequestFiles,
ModelBuilderProtocol,
not_given,
Expand Down Expand Up @@ -478,8 +481,19 @@ def _build_request(
retries_taken: int = 0,
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
log.debug("Request options: %s", model_dump(options, exclude_unset=True))

log.debug(
"Request options: %s",
model_dump(
options,
exclude_unset=True,
# Pydantic v1 can't dump every type we support in content, so we exclude it for now.
exclude={
"content",
}
if PYDANTIC_V1
else {},
),
)
kwargs: dict[str, Any] = {}

json_data = options.json_data
Expand Down Expand Up @@ -533,7 +547,13 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"

if is_body_allowed:
if isinstance(json_data, bytes):
if options.content is not None and json_data is not None:
raise TypeError("Passing both `content` and `json_data` is not supported")
if options.content is not None and files is not None:
raise TypeError("Passing both `content` and `files` is not supported")
if options.content is not None:
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
Expand Down Expand Up @@ -1209,6 +1229,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[False] = False,
Expand All @@ -1221,6 +1242,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[True],
Expand All @@ -1234,6 +1256,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool,
Expand All @@ -1246,13 +1269,25 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="post", url=path, json_data=body, files=to_httpx_files(files), **options
method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))

Expand All @@ -1262,11 +1297,23 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)

Expand All @@ -1276,11 +1323,23 @@ def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="put", url=path, json_data=body, files=to_httpx_files(files), **options
method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)

Expand All @@ -1290,9 +1349,19 @@ def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: BinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return self.request(cast_to, opts)

def get_api_list(
Expand Down Expand Up @@ -1746,6 +1815,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[False] = False,
Expand All @@ -1758,6 +1828,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[True],
Expand All @@ -1771,6 +1842,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool,
Expand All @@ -1783,13 +1855,25 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
) -> ResponseT | _AsyncStreamT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)

Expand All @@ -1799,11 +1883,28 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
method="patch",
url=path,
json_data=body,
content=content,
files=await async_to_httpx_files(files),
**options,
)
return await self.request(cast_to, opts)

Expand All @@ -1813,11 +1914,23 @@ async def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if files is not None and content is not None:
raise TypeError("Passing both `files` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(
method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)

Expand All @@ -1827,9 +1940,19 @@ async def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
content: AsyncBinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
if body is not None and content is not None:
raise TypeError("Passing both `body` and `content` is not supported")
if isinstance(body, bytes):
warnings.warn(
"Passing raw bytes as `body` is deprecated and will be removed in a future version. "
"Please pass raw bytes via the `content` parameter instead.",
DeprecationWarning,
stacklevel=2,
)
opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return await self.request(cast_to, opts)

def get_api_list(
Expand Down
17 changes: 16 additions & 1 deletion src/lithic/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@
import os
import inspect
import weakref
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from typing import (
IO,
TYPE_CHECKING,
Any,
Type,
Union,
Generic,
TypeVar,
Callable,
Iterable,
Optional,
AsyncIterable,
cast,
)
from datetime import date, datetime
from typing_extensions import (
List,
Expand Down Expand Up @@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
json_data: Body
extra_json: AnyMapping
follow_redirects: bool
Expand All @@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel):
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None

content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
json_data: Union[Body, None] = None
Expand Down
Loading