Skip to content
Merged
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: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ jobs:
lint:
name: lint
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

Expand All @@ -33,7 +32,6 @@ jobs:
test:
name: test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

Expand Down
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.3.2"
".": "0.3.3"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 2
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-861e8a85f0fb73cf4b7fc6c2b27722072ff33109459e90c17be24af15dfcbd0c.yml
openapi_spec_hash: 644a0383600633ee604bb1e5b9ca025d
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-d58ccd91625a3b12fd8d1ceece128b604010bd840096000287c927cb5dcf79eb.yml
openapi_spec_hash: 22c8c973d55f26649e9df96c89ea537f
config_hash: 1d603d50b7183a492ad6df5f728a1863
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## 0.3.3 (2025-04-16)

Full Changelog: [v0.3.2...v0.3.3](https://github.com/isaacus-dev/isaacus-python/compare/v0.3.2...v0.3.3)

### Bug Fixes

* **perf:** optimize some hot paths ([eee757b](https://github.com/isaacus-dev/isaacus-python/commit/eee757ba44a895fcf2052b9981783b6cf233653f))
* **perf:** skip traversing types for NotGiven values ([7705a99](https://github.com/isaacus-dev/isaacus-python/commit/7705a99e0efd9724eb3260550b4b58081af85878))


### Chores

* **client:** minor internal fixes ([a8dad58](https://github.com/isaacus-dev/isaacus-python/commit/a8dad5881d0f3f5d1929574efba483a8fcdbc322))
* **internal:** codegen related update ([93cdfa0](https://github.com/isaacus-dev/isaacus-python/commit/93cdfa0c0dfc947ec76f10291887b90324301b32))
* **internal:** expand CI branch coverage ([cc5df77](https://github.com/isaacus-dev/isaacus-python/commit/cc5df7771a9ea699b0e37533070e1cb5569d7ad9))
* **internal:** reduce CI branch coverage ([2cb8fb8](https://github.com/isaacus-dev/isaacus-python/commit/2cb8fb81f4cea76d12ae3feeb09e4b43b743e8c4))
* **internal:** slight transform perf improvement ([6f47eaf](https://github.com/isaacus-dev/isaacus-python/commit/6f47eafa0ebcd31741f24bea539a4c54e88a758e))
* **internal:** update pyright settings ([7dd9ad4](https://github.com/isaacus-dev/isaacus-python/commit/7dd9ad4a4a25825929a4916168a07d74bcc52fbe))


### Documentation

* **api:** removed description of certain objects due to Mintlify bug ([9099926](https://github.com/isaacus-dev/isaacus-python/commit/90999261a360fef3ba92c52e4ad5361b79b499e6))

## 0.3.2 (2025-04-04)

Full Changelog: [v0.3.1...v0.3.2](https://github.com/isaacus-dev/isaacus-python/compare/v0.3.1...v0.3.2)
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "isaacus"
version = "0.3.2"
version = "0.3.3"
description = "The official Python library for the isaacus API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down Expand Up @@ -147,6 +147,7 @@ exclude = [
]

reportImplicitOverride = true
reportOverlappingOverload = false

reportImportCycles = false
reportPrivateUsage = false
Expand Down
11 changes: 10 additions & 1 deletion src/isaacus/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0

idempotency_header = self._idempotency_header
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
options.idempotency_key = options.idempotency_key or self._idempotency_key()
headers[idempotency_header] = options.idempotency_key

# Don't set these headers if they were already set or removed by the caller. We check
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
Expand Down Expand Up @@ -943,6 +944,10 @@ def _request(
request = self._build_request(options, retries_taken=retries_taken)
self._prepare_request(request)

if options.idempotency_key:
# ensure the idempotency key is reused between requests
input_options.idempotency_key = options.idempotency_key

kwargs: HttpxSendArgs = {}
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth
Expand Down Expand Up @@ -1475,6 +1480,10 @@ async def _request(
request = self._build_request(options, retries_taken=retries_taken)
await self._prepare_request(request)

if options.idempotency_key:
# ensure the idempotency key is reused between requests
input_options.idempotency_key = options.idempotency_key

kwargs: HttpxSendArgs = {}
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth
Expand Down
47 changes: 46 additions & 1 deletion src/isaacus/_utils/_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
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
from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints

import anyio
import pydantic

from ._utils import (
is_list,
is_given,
lru_cache,
is_mapping,
is_iterable,
)
Expand Down Expand Up @@ -108,6 +110,7 @@ class Params(TypedDict, total=False):
return cast(_T, transformed)


@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
"""If the given type is an `Annotated` type then it is returned, if not `None` is returned.

Expand Down Expand Up @@ -142,6 +145,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
return key


def _no_transform_needed(annotation: type) -> bool:
return annotation == float or annotation == int


def _transform_recursive(
data: object,
*,
Expand Down Expand Up @@ -184,6 +191,15 @@ def _transform_recursive(
return cast(object, data)

inner_type = extract_type_arg(stripped_type, 0)
if _no_transform_needed(inner_type):
# for some types there is no need to transform anything, so we can get a small
# perf boost from skipping that work.
#
# but we still need to convert to a list to ensure the data is json-serializable
if is_list(data):
return data
return list(data)

return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

if is_union_type(stripped_type):
Expand Down Expand Up @@ -245,6 +261,11 @@ def _transform_typeddict(
result: dict[str, object] = {}
annotations = get_type_hints(expected_type, include_extras=True)
for key, value in data.items():
if not is_given(value):
# we don't need to include `NotGiven` values here as they'll
# be stripped out before the request is sent anyway
continue

type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
Expand Down Expand Up @@ -332,6 +353,15 @@ async def _async_transform_recursive(
return cast(object, data)

inner_type = extract_type_arg(stripped_type, 0)
if _no_transform_needed(inner_type):
# for some types there is no need to transform anything, so we can get a small
# perf boost from skipping that work.
#
# but we still need to convert to a list to ensure the data is json-serializable
if is_list(data):
return data
return list(data)

return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]

if is_union_type(stripped_type):
Expand Down Expand Up @@ -393,10 +423,25 @@ async def _async_transform_typeddict(
result: dict[str, object] = {}
annotations = get_type_hints(expected_type, include_extras=True)
for key, value in data.items():
if not is_given(value):
# we don't need to include `NotGiven` values here as they'll
# be stripped out before the request is sent anyway
continue

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


@lru_cache(maxsize=8096)
def get_type_hints(
obj: Any,
globalns: dict[str, Any] | None = None,
localns: Mapping[str, Any] | None = None,
include_extras: bool = False,
) -> dict[str, Any]:
return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
2 changes: 2 additions & 0 deletions src/isaacus/_utils/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
get_origin,
)

from ._utils import lru_cache
from .._types import InheritsGeneric
from .._compat import is_union as _is_union

Expand Down Expand Up @@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:


# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
if is_required_type(typ) or is_annotated_type(typ):
return strip_annotated_type(cast(type, get_args(typ)[0]))
Expand Down
2 changes: 1 addition & 1 deletion src/isaacus/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "isaacus"
__version__ = "0.3.2" # x-release-please-version
__version__ = "0.3.3" # x-release-please-version
21 changes: 20 additions & 1 deletion tests/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import pytest

from isaacus._types import Base64FileInput
from isaacus._types import NOT_GIVEN, Base64FileInput
from isaacus._utils import (
PropertyInfo,
transform as _transform,
Expand Down Expand Up @@ -432,3 +432,22 @@ async def test_base64_file_input(use_async: bool) -> None:
assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
"foo": "SGVsbG8sIHdvcmxkIQ=="
} # type: ignore[comparison-overlap]


@parametrize
@pytest.mark.asyncio
async def test_transform_skipping(use_async: bool) -> None:
# lists of ints are left as-is
data = [1, 2, 3]
assert await transform(data, List[int], use_async) is data

# iterables of ints are converted to a list
data = iter([1, 2, 3])
assert await transform(data, Iterable[int], use_async) == [1, 2, 3]


@parametrize
@pytest.mark.asyncio
async def test_strips_notgiven(use_async: bool) -> None:
assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}