From 2ab398dde07e98411c9b6efd76f7b7120a9633a8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 06:18:23 +0000 Subject: [PATCH 01/38] feat(client): support file upload requests --- src/isaacus/_base_client.py | 5 ++++- src/isaacus/_files.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/isaacus/_base_client.py b/src/isaacus/_base_client.py index a77dced..fae79aa 100644 --- a/src/isaacus/_base_client.py +++ b/src/isaacus/_base_client.py @@ -507,7 +507,10 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - kwargs["json"] = json_data if is_given(json_data) else None + if isinstance(json_data, bytes): + kwargs["content"] = json_data + else: + kwargs["json"] = json_data if is_given(json_data) else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/isaacus/_files.py b/src/isaacus/_files.py index 715cc20..cc14c14 100644 --- a/src/isaacus/_files.py +++ b/src/isaacus/_files.py @@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], _read_file_content(file[1]), *file[2:]) + return (file[0], read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -def _read_file_content(file: FileContent) -> HttpxFileContent: +def read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return pathlib.Path(file).read_bytes() return file @@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes: return file if is_tuple_t(file): - return (file[0], await _async_read_file_content(file[1]), *file[2:]) + return (file[0], await async_read_file_content(file[1]), *file[2:]) raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple") -async def _async_read_file_content(file: FileContent) -> HttpxFileContent: +async def async_read_file_content(file: FileContent) -> HttpxFileContent: if isinstance(file, os.PathLike): return await anyio.Path(file).read_bytes() From 889d576cdc28d06404c6ee3ce0c67bf4d3be75c4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 07:53:46 +0000 Subject: [PATCH 02/38] chore(internal): fix ruff target version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4b1c051..4ada304 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,7 +159,7 @@ reportPrivateUsage = false [tool.ruff] line-length = 120 output-format = "grouped" -target-version = "py37" +target-version = "py38" [tool.ruff.format] docstring-code-format = true From a3141f59b0ff6334fde2a9740fd2f86824fe5083 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 04:46:10 +0000 Subject: [PATCH 03/38] chore: update @stainless-api/prism-cli to v5.15.0 --- scripts/mock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/mock b/scripts/mock index d2814ae..0b28f6e 100755 --- a/scripts/mock +++ b/scripts/mock @@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & # Wait for server to come online echo -n "Waiting for server" @@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" fi From 7af966e1677b44d412eda96c5ee8e9866f77ccfb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 04:49:23 +0000 Subject: [PATCH 04/38] chore(internal): update comment in script --- scripts/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test b/scripts/test index 2b87845..dbeda2d 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! prism_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the prism command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" echo exit 1 From 22b520b3c67e570f9267135111a89542ee2bdf7f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:18:56 +0000 Subject: [PATCH 05/38] chore(internal): codegen related update --- .../classifications/test_universal.py | 16 ++++++++-------- tests/api_resources/extractions/test_qa.py | 16 ++++++++-------- tests/api_resources/test_rerankings.py | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/api_resources/classifications/test_universal.py b/tests/api_resources/classifications/test_universal.py index b023304..ec481a9 100644 --- a/tests/api_resources/classifications/test_universal.py +++ b/tests/api_resources/classifications/test_universal.py @@ -17,7 +17,7 @@ class TestUniversal: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Isaacus) -> None: universal = client.classifications.universal.create( @@ -27,7 +27,7 @@ def test_method_create(self, client: Isaacus) -> None: ) assert_matches_type(UniversalClassification, universal, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Isaacus) -> None: universal = client.classifications.universal.create( @@ -44,7 +44,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: ) assert_matches_type(UniversalClassification, universal, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Isaacus) -> None: response = client.classifications.universal.with_raw_response.create( @@ -58,7 +58,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: universal = response.parse() assert_matches_type(UniversalClassification, universal, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Isaacus) -> None: with client.classifications.universal.with_streaming_response.create( @@ -80,7 +80,7 @@ class TestAsyncUniversal: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncIsaacus) -> None: universal = await async_client.classifications.universal.create( @@ -90,7 +90,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: ) assert_matches_type(UniversalClassification, universal, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: universal = await async_client.classifications.universal.create( @@ -107,7 +107,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - ) assert_matches_type(UniversalClassification, universal, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: response = await async_client.classifications.universal.with_raw_response.create( @@ -121,7 +121,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: universal = await response.parse() assert_matches_type(UniversalClassification, universal, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: async with async_client.classifications.universal.with_streaming_response.create( diff --git a/tests/api_resources/extractions/test_qa.py b/tests/api_resources/extractions/test_qa.py index 476997e..1c389a1 100644 --- a/tests/api_resources/extractions/test_qa.py +++ b/tests/api_resources/extractions/test_qa.py @@ -17,7 +17,7 @@ class TestQa: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Isaacus) -> None: qa = client.extractions.qa.create( @@ -29,7 +29,7 @@ def test_method_create(self, client: Isaacus) -> None: ) assert_matches_type(AnswerExtraction, qa, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Isaacus) -> None: qa = client.extractions.qa.create( @@ -48,7 +48,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: ) assert_matches_type(AnswerExtraction, qa, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Isaacus) -> None: response = client.extractions.qa.with_raw_response.create( @@ -64,7 +64,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: qa = response.parse() assert_matches_type(AnswerExtraction, qa, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Isaacus) -> None: with client.extractions.qa.with_streaming_response.create( @@ -88,7 +88,7 @@ class TestAsyncQa: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncIsaacus) -> None: qa = await async_client.extractions.qa.create( @@ -100,7 +100,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: ) assert_matches_type(AnswerExtraction, qa, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: qa = await async_client.extractions.qa.create( @@ -119,7 +119,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - ) assert_matches_type(AnswerExtraction, qa, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: response = await async_client.extractions.qa.with_raw_response.create( @@ -135,7 +135,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: qa = await response.parse() assert_matches_type(AnswerExtraction, qa, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: async with async_client.extractions.qa.with_streaming_response.create( diff --git a/tests/api_resources/test_rerankings.py b/tests/api_resources/test_rerankings.py index 9ceb5ab..845def9 100644 --- a/tests/api_resources/test_rerankings.py +++ b/tests/api_resources/test_rerankings.py @@ -17,7 +17,7 @@ class TestRerankings: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Isaacus) -> None: reranking = client.rerankings.create( @@ -33,7 +33,7 @@ def test_method_create(self, client: Isaacus) -> None: ) assert_matches_type(Reranking, reranking, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Isaacus) -> None: reranking = client.rerankings.create( @@ -57,7 +57,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: ) assert_matches_type(Reranking, reranking, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Isaacus) -> None: response = client.rerankings.with_raw_response.create( @@ -77,7 +77,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: reranking = response.parse() assert_matches_type(Reranking, reranking, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Isaacus) -> None: with client.rerankings.with_streaming_response.create( @@ -105,7 +105,7 @@ class TestAsyncRerankings: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncIsaacus) -> None: reranking = await async_client.rerankings.create( @@ -121,7 +121,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: ) assert_matches_type(Reranking, reranking, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: reranking = await async_client.rerankings.create( @@ -145,7 +145,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - ) assert_matches_type(Reranking, reranking, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: response = await async_client.rerankings.with_raw_response.create( @@ -165,7 +165,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: reranking = await response.parse() assert_matches_type(Reranking, reranking, path=["response"]) - @pytest.mark.skip() + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: async with async_client.rerankings.with_streaming_response.create( From 05180288265bc111dba1c62fbfcd90139a6299ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 06:55:50 +0000 Subject: [PATCH 06/38] chore: update github action --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 114b4f2..b1e5cd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: ./scripts/lint build: - if: github.repository == 'stainless-sdks/isaacus-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork timeout-minutes: 10 name: build permissions: @@ -61,12 +61,14 @@ jobs: run: rye build - name: Get GitHub OIDC Token + if: github.repository == 'stainless-sdks/isaacus-python' id: github-oidc uses: actions/github-script@v6 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Upload tarball + if: github.repository == 'stainless-sdks/isaacus-python' env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} From f86cbcef2583658466e95eaba4aba61f79646ef9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 05:29:31 +0000 Subject: [PATCH 07/38] chore(internal): change ci workflow machines --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1e5cd5..a1907b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: permissions: contents: read id-token: write - runs-on: depot-ubuntu-24.04 + runs-on: ${{ github.repository == 'stainless-sdks/isaacus-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@v4 From 10253fe93ed8142b52cf5199486221e81ac6ce5a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 08:02:08 +0000 Subject: [PATCH 08/38] fix: avoid newer type syntax --- src/isaacus/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/isaacus/_models.py b/src/isaacus/_models.py index b8387ce..92f7c10 100644 --- a/src/isaacus/_models.py +++ b/src/isaacus/_models.py @@ -304,7 +304,7 @@ def model_dump( exclude_none=exclude_none, ) - return cast(dict[str, Any], json_safe(dumped)) if mode == "json" else dumped + return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped @override def model_dump_json( From 6f0ae86899883fe77aa669d595c623bedc2dc5c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 08:05:35 +0000 Subject: [PATCH 09/38] chore(internal): update pyright exclude list --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 4ada304..65e0867 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,6 +148,7 @@ exclude = [ "_dev", ".venv", ".nox", + ".git", ] reportImplicitOverride = true From 5a2287ef854d250048c070f3fd88b00ca84b0d3c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 04:10:45 +0000 Subject: [PATCH 10/38] chore(internal): add Sequence related utils --- src/isaacus/_types.py | 36 +++++++++++++++++++++++++++++++++- src/isaacus/_utils/__init__.py | 1 + src/isaacus/_utils/_typing.py | 5 +++++ tests/utils.py | 10 +++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/isaacus/_types.py b/src/isaacus/_types.py index 82c0bc0..5b999d6 100644 --- a/src/isaacus/_types.py +++ b/src/isaacus/_types.py @@ -13,10 +13,21 @@ Mapping, TypeVar, Callable, + Iterator, Optional, Sequence, ) -from typing_extensions import Set, Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable +from typing_extensions import ( + Set, + Literal, + Protocol, + TypeAlias, + TypedDict, + SupportsIndex, + overload, + override, + runtime_checkable, +) import httpx import pydantic @@ -217,3 +228,26 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth follow_redirects: bool + + +_T_co = TypeVar("_T_co", covariant=True) + + +if TYPE_CHECKING: + # This works because str.__contains__ does not accept object (either in typeshed or at runtime) + # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + class SequenceNotStr(Protocol[_T_co]): + @overload + def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... + @overload + def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... + def __contains__(self, value: object, /) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_co]: ... + def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... + def count(self, value: Any, /) -> int: ... + def __reversed__(self) -> Iterator[_T_co]: ... +else: + # just point this to a normal `Sequence` at runtime to avoid having to special case + # deserializing our custom sequence type + SequenceNotStr = Sequence diff --git a/src/isaacus/_utils/__init__.py b/src/isaacus/_utils/__init__.py index d4fda26..ca547ce 100644 --- a/src/isaacus/_utils/__init__.py +++ b/src/isaacus/_utils/__init__.py @@ -38,6 +38,7 @@ extract_type_arg as extract_type_arg, is_iterable_type as is_iterable_type, is_required_type as is_required_type, + is_sequence_type as is_sequence_type, is_annotated_type as is_annotated_type, is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, diff --git a/src/isaacus/_utils/_typing.py b/src/isaacus/_utils/_typing.py index 1bac954..845cd6b 100644 --- a/src/isaacus/_utils/_typing.py +++ b/src/isaacus/_utils/_typing.py @@ -26,6 +26,11 @@ def is_list_type(typ: type) -> bool: return (get_origin(typ) or typ) == list +def is_sequence_type(typ: type) -> bool: + origin = get_origin(typ) or typ + return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence + + def is_iterable_type(typ: type) -> bool: """If the given type is `typing.Iterable[T]`""" origin = get_origin(typ) or typ diff --git a/tests/utils.py b/tests/utils.py index 8584eb6..18a607d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,7 @@ import inspect import traceback import contextlib -from typing import Any, TypeVar, Iterator, cast +from typing import Any, TypeVar, Iterator, Sequence, cast from datetime import date, datetime from typing_extensions import Literal, get_args, get_origin, assert_type @@ -15,6 +15,7 @@ is_list_type, is_union_type, extract_type_arg, + is_sequence_type, is_annotated_type, is_type_alias_type, ) @@ -71,6 +72,13 @@ def assert_matches_type( if is_list_type(type_): return _assert_list_type(type_, value) + if is_sequence_type(type_): + assert isinstance(value, Sequence) + inner_type = get_args(type_)[0] + for entry in value: # type: ignore + assert_type(inner_type, entry) # type: ignore + return + if origin == str: assert isinstance(value, str) elif origin == int: From d2733a9d0f16531537a9db017a8e29d2c8fb3912 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 03:41:30 +0000 Subject: [PATCH 11/38] feat(types): replace List[str] with SequenceNotStr in params --- src/isaacus/_utils/_transform.py | 6 ++++++ src/isaacus/resources/classifications/universal.py | 8 ++++---- src/isaacus/resources/extractions/qa.py | 8 ++++---- src/isaacus/resources/rerankings.py | 8 ++++---- .../types/classifications/universal_create_params.py | 6 ++++-- src/isaacus/types/extractions/qa_create_params.py | 6 ++++-- src/isaacus/types/reranking_create_params.py | 6 ++++-- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/isaacus/_utils/_transform.py b/src/isaacus/_utils/_transform.py index b0cc20a..f0bcefd 100644 --- a/src/isaacus/_utils/_transform.py +++ b/src/isaacus/_utils/_transform.py @@ -16,6 +16,7 @@ lru_cache, is_mapping, is_iterable, + is_sequence, ) from .._files import is_base64_file_input from ._typing import ( @@ -24,6 +25,7 @@ extract_type_arg, is_iterable_type, is_required_type, + is_sequence_type, is_annotated_type, strip_annotated_type, ) @@ -184,6 +186,8 @@ def _transform_recursive( (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)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. @@ -346,6 +350,8 @@ async def _async_transform_recursive( (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)) + # Sequence[T] + or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str)) ): # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually # intended as an iterable, so we don't transform it. diff --git a/src/isaacus/resources/classifications/universal.py b/src/isaacus/resources/classifications/universal.py index 54fb538..eab1539 100644 --- a/src/isaacus/resources/classifications/universal.py +++ b/src/isaacus/resources/classifications/universal.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -49,7 +49,7 @@ def create( *, model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, - texts: List[str], + texts: SequenceNotStr[str], chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, is_iql: bool | NotGiven = NOT_GIVEN, scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, @@ -150,7 +150,7 @@ async def create( *, model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, - texts: List[str], + texts: SequenceNotStr[str], chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, is_iql: bool | NotGiven = NOT_GIVEN, scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, diff --git a/src/isaacus/resources/extractions/qa.py b/src/isaacus/resources/extractions/qa.py index bda699f..1fd5652 100644 --- a/src/isaacus/resources/extractions/qa.py +++ b/src/isaacus/resources/extractions/qa.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -49,7 +49,7 @@ def create( *, model: Literal["kanon-answer-extractor", "kanon-answer-extractor-mini"], query: str, - texts: List[str], + texts: SequenceNotStr[str], chunking_options: Optional[qa_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, ignore_inextractability: bool | NotGiven = NOT_GIVEN, top_k: int | NotGiven = NOT_GIVEN, @@ -148,7 +148,7 @@ async def create( *, model: Literal["kanon-answer-extractor", "kanon-answer-extractor-mini"], query: str, - texts: List[str], + texts: SequenceNotStr[str], chunking_options: Optional[qa_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, ignore_inextractability: bool | NotGiven = NOT_GIVEN, top_k: int | NotGiven = NOT_GIVEN, diff --git a/src/isaacus/resources/rerankings.py b/src/isaacus/resources/rerankings.py index 09561e2..bd8de95 100644 --- a/src/isaacus/resources/rerankings.py +++ b/src/isaacus/resources/rerankings.py @@ -2,13 +2,13 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal import httpx from ..types import reranking_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -49,7 +49,7 @@ def create( *, model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, - texts: List[str], + texts: SequenceNotStr[str], chunking_options: Optional[reranking_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, is_iql: bool | NotGiven = NOT_GIVEN, scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, @@ -160,7 +160,7 @@ async def create( *, model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, - texts: List[str], + texts: SequenceNotStr[str], chunking_options: Optional[reranking_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, is_iql: bool | NotGiven = NOT_GIVEN, scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, diff --git a/src/isaacus/types/classifications/universal_create_params.py b/src/isaacus/types/classifications/universal_create_params.py index 23ed39e..167737a 100644 --- a/src/isaacus/types/classifications/universal_create_params.py +++ b/src/isaacus/types/classifications/universal_create_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["UniversalCreateParams", "ChunkingOptions"] @@ -26,7 +28,7 @@ class UniversalCreateParams(TypedDict, total=False): the maximum input length of the universal classifier. """ - texts: Required[List[str]] + texts: Required[SequenceNotStr[str]] """The texts to classify. Each text must contain at least one non-whitespace character. diff --git a/src/isaacus/types/extractions/qa_create_params.py b/src/isaacus/types/extractions/qa_create_params.py index 07b3e79..85b34b2 100644 --- a/src/isaacus/types/extractions/qa_create_params.py +++ b/src/isaacus/types/extractions/qa_create_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict +from ..._types import SequenceNotStr + __all__ = ["QaCreateParams", "ChunkingOptions"] @@ -24,7 +26,7 @@ class QaCreateParams(TypedDict, total=False): long that it exceeds the maximum input length of the model. """ - texts: Required[List[str]] + texts: Required[SequenceNotStr[str]] """The texts to search for the answer in and extract the answer from. There must be at least one text. diff --git a/src/isaacus/types/reranking_create_params.py b/src/isaacus/types/reranking_create_params.py index 4c62677..7f7f800 100644 --- a/src/isaacus/types/reranking_create_params.py +++ b/src/isaacus/types/reranking_create_params.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import List, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict +from .._types import SequenceNotStr + __all__ = ["RerankingCreateParams", "ChunkingOptions"] @@ -24,7 +26,7 @@ class RerankingCreateParams(TypedDict, total=False): maximum input length of the reranker. """ - texts: Required[List[str]] + texts: Required[SequenceNotStr[str]] """The texts to rerank. There must be at least one text. From 5a20497a9c4bbf88056df12a0c686566dc9bd162 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 03:42:32 +0000 Subject: [PATCH 12/38] feat: improve future compat with pydantic v3 --- src/isaacus/_base_client.py | 6 +- src/isaacus/_compat.py | 96 ++++++++--------- src/isaacus/_models.py | 80 +++++++------- src/isaacus/_utils/__init__.py | 10 +- src/isaacus/_utils/_compat.py | 45 ++++++++ src/isaacus/_utils/_datetime_parse.py | 136 ++++++++++++++++++++++++ src/isaacus/_utils/_transform.py | 6 +- src/isaacus/_utils/_typing.py | 2 +- src/isaacus/_utils/_utils.py | 1 - tests/test_models.py | 48 ++++----- tests/test_transform.py | 16 +-- tests/test_utils/test_datetime_parse.py | 110 +++++++++++++++++++ tests/utils.py | 8 +- 13 files changed, 432 insertions(+), 132 deletions(-) create mode 100644 src/isaacus/_utils/_compat.py create mode 100644 src/isaacus/_utils/_datetime_parse.py create mode 100644 tests/test_utils/test_datetime_parse.py diff --git a/src/isaacus/_base_client.py b/src/isaacus/_base_client.py index fae79aa..d6d847b 100644 --- a/src/isaacus/_base_client.py +++ b/src/isaacus/_base_client.py @@ -59,7 +59,7 @@ ModelBuilderProtocol, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import PYDANTIC_V2, model_copy, model_dump +from ._compat import PYDANTIC_V1, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -207,7 +207,7 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model @@ -295,7 +295,7 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: - if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None: self.__pydantic_private__ = {} self._model = model diff --git a/src/isaacus/_compat.py b/src/isaacus/_compat.py index 92d9ee6..bdef67f 100644 --- a/src/isaacus/_compat.py +++ b/src/isaacus/_compat.py @@ -12,14 +12,13 @@ _T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) -# --------------- Pydantic v2 compatibility --------------- +# --------------- Pydantic v2, v3 compatibility --------------- # Pyright incorrectly reports some of our functions as overriding a method when they don't # pyright: reportIncompatibleMethodOverride=false -PYDANTIC_V2 = pydantic.VERSION.startswith("2.") +PYDANTIC_V1 = pydantic.VERSION.startswith("1.") -# v1 re-exports if TYPE_CHECKING: def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001 @@ -44,90 +43,92 @@ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001 ... else: - if PYDANTIC_V2: - from pydantic.v1.typing import ( + # v1 re-exports + if PYDANTIC_V1: + from pydantic.typing import ( get_args as get_args, is_union as is_union, get_origin as get_origin, is_typeddict as is_typeddict, is_literal_type as is_literal_type, ) - from pydantic.v1.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime + from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime else: - from pydantic.typing import ( + from ._utils import ( get_args as get_args, is_union as is_union, get_origin as get_origin, + parse_date as parse_date, is_typeddict as is_typeddict, + parse_datetime as parse_datetime, is_literal_type as is_literal_type, ) - from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime # refactored config if TYPE_CHECKING: from pydantic import ConfigDict as ConfigDict else: - if PYDANTIC_V2: - from pydantic import ConfigDict - else: + if PYDANTIC_V1: # TODO: provide an error message here? ConfigDict = None + else: + from pydantic import ConfigDict as ConfigDict # renamed methods / properties def parse_obj(model: type[_ModelT], value: object) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(value) - else: + if PYDANTIC_V1: return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast] + else: + return model.model_validate(value) def field_is_required(field: FieldInfo) -> bool: - if PYDANTIC_V2: - return field.is_required() - return field.required # type: ignore + if PYDANTIC_V1: + return field.required # type: ignore + return field.is_required() def field_get_default(field: FieldInfo) -> Any: value = field.get_default() - if PYDANTIC_V2: - from pydantic_core import PydanticUndefined - - if value == PydanticUndefined: - return None + if PYDANTIC_V1: return value + from pydantic_core import PydanticUndefined + + if value == PydanticUndefined: + return None return value def field_outer_type(field: FieldInfo) -> Any: - if PYDANTIC_V2: - return field.annotation - return field.outer_type_ # type: ignore + if PYDANTIC_V1: + return field.outer_type_ # type: ignore + return field.annotation def get_model_config(model: type[pydantic.BaseModel]) -> Any: - if PYDANTIC_V2: - return model.model_config - return model.__config__ # type: ignore + if PYDANTIC_V1: + return model.__config__ # type: ignore + return model.model_config def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]: - if PYDANTIC_V2: - return model.model_fields - return model.__fields__ # type: ignore + if PYDANTIC_V1: + return model.__fields__ # type: ignore + return model.model_fields def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT: - if PYDANTIC_V2: - return model.model_copy(deep=deep) - return model.copy(deep=deep) # type: ignore + if PYDANTIC_V1: + return model.copy(deep=deep) # type: ignore + return model.model_copy(deep=deep) def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str: - if PYDANTIC_V2: - return model.model_dump_json(indent=indent) - return model.json(indent=indent) # type: ignore + if PYDANTIC_V1: + return model.json(indent=indent) # type: ignore + return model.model_dump_json(indent=indent) def model_dump( @@ -139,14 +140,14 @@ def model_dump( warnings: bool = True, mode: Literal["json", "python"] = "python", ) -> dict[str, Any]: - if PYDANTIC_V2 or hasattr(model, "model_dump"): + if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( mode=mode, exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 - warnings=warnings if PYDANTIC_V2 else True, + warnings=True if PYDANTIC_V1 else warnings, ) return cast( "dict[str, Any]", @@ -159,9 +160,9 @@ def model_dump( def model_parse(model: type[_ModelT], data: Any) -> _ModelT: - if PYDANTIC_V2: - return model.model_validate(data) - return model.parse_obj(data) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return model.parse_obj(data) # pyright: ignore[reportDeprecated] + return model.model_validate(data) # generic models @@ -170,17 +171,16 @@ def model_parse(model: type[_ModelT], data: Any) -> _ModelT: class GenericModel(pydantic.BaseModel): ... else: - if PYDANTIC_V2: + if PYDANTIC_V1: + import pydantic.generics + + class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... + else: # there no longer needs to be a distinction in v2 but # we still have to create our own subclass to avoid # inconsistent MRO ordering errors class GenericModel(pydantic.BaseModel): ... - else: - import pydantic.generics - - class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ... - # cached properties if TYPE_CHECKING: diff --git a/src/isaacus/_models.py b/src/isaacus/_models.py index 92f7c10..3a6017e 100644 --- a/src/isaacus/_models.py +++ b/src/isaacus/_models.py @@ -50,7 +50,7 @@ strip_annotated_type, ) from ._compat import ( - PYDANTIC_V2, + PYDANTIC_V1, ConfigDict, GenericModel as BaseGenericModel, get_args, @@ -81,11 +81,7 @@ class _ConfigProtocol(Protocol): class BaseModel(pydantic.BaseModel): - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) - ) - else: + if PYDANTIC_V1: @property @override @@ -95,6 +91,10 @@ def model_fields_set(self) -> set[str]: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore + else: + model_config: ClassVar[ConfigDict] = ConfigDict( + extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + ) def to_dict( self, @@ -215,25 +215,25 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] if key not in model_fields: parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value - if PYDANTIC_V2: - _extra[key] = parsed - else: + if PYDANTIC_V1: _fields_set.add(key) fields_values[key] = parsed + else: + _extra[key] = parsed object.__setattr__(m, "__dict__", fields_values) - if PYDANTIC_V2: - # these properties are copied from Pydantic's `model_construct()` method - object.__setattr__(m, "__pydantic_private__", None) - object.__setattr__(m, "__pydantic_extra__", _extra) - object.__setattr__(m, "__pydantic_fields_set__", _fields_set) - else: + if PYDANTIC_V1: # init_private_attributes() does not exist in v2 m._init_private_attributes() # type: ignore # copied from Pydantic v1's `construct()` method object.__setattr__(m, "__fields_set__", _fields_set) + else: + # these properties are copied from Pydantic's `model_construct()` method + object.__setattr__(m, "__pydantic_private__", None) + object.__setattr__(m, "__pydantic_extra__", _extra) + object.__setattr__(m, "__pydantic_fields_set__", _fields_set) return m @@ -243,7 +243,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] # although not in practice model_construct = construct - if not PYDANTIC_V2: + if PYDANTIC_V1: # we define aliases for some of the new pydantic v2 methods so # that we can just document these methods without having to specify # a specific pydantic version as some users may not know which @@ -363,10 +363,10 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) - if PYDANTIC_V2: - type_ = field.annotation - else: + if PYDANTIC_V1: type_ = cast(type, field.outer_type_) # type: ignore + else: + type_ = field.annotation # type: ignore if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") @@ -375,7 +375,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None: - if not PYDANTIC_V2: + if PYDANTIC_V1: # TODO return None @@ -628,30 +628,30 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, 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: + if PYDANTIC_V1: + 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.get("serialization_alias") - - field_schema = field["schema"] + discriminator_alias = field_info.alias - if field_schema["type"] == "literal": - for entry in cast("LiteralSchema", field_schema)["expected"]: + if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): + for entry in get_args(annotation): 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: + 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_info.alias + discriminator_alias = field.get("serialization_alias") - if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation): - for entry in get_args(annotation): + field_schema = field["schema"] + + if field_schema["type"] == "literal": + for entry in cast("LiteralSchema", field_schema)["expected"]: if isinstance(entry, str): mapping[entry] = variant @@ -714,7 +714,7 @@ class GenericModel(BaseGenericModel, BaseModel): pass -if PYDANTIC_V2: +if not PYDANTIC_V1: from pydantic import TypeAdapter as _TypeAdapter _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter)) @@ -782,12 +782,12 @@ class FinalRequestOptions(pydantic.BaseModel): json_data: Union[Body, None] = None extra_json: Union[AnyMapping, None] = None - if PYDANTIC_V2: - model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) - else: + if PYDANTIC_V1: class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] arbitrary_types_allowed: bool = True + else: + model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True) def get_max_retries(self, max_retries: int) -> int: if isinstance(self.max_retries, NotGiven): @@ -820,9 +820,9 @@ def construct( # type: ignore key: strip_not_given(value) for key, value in values.items() } - if PYDANTIC_V2: - return super().model_construct(_fields_set, **kwargs) - return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + if PYDANTIC_V1: + return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated] + return super().model_construct(_fields_set, **kwargs) if not TYPE_CHECKING: # type checkers incorrectly complain about this assignment diff --git a/src/isaacus/_utils/__init__.py b/src/isaacus/_utils/__init__.py index ca547ce..dc64e29 100644 --- a/src/isaacus/_utils/__init__.py +++ b/src/isaacus/_utils/__init__.py @@ -10,7 +10,6 @@ lru_cache as lru_cache, is_mapping as is_mapping, is_tuple_t as is_tuple_t, - parse_date as parse_date, is_iterable as is_iterable, is_sequence as is_sequence, coerce_float as coerce_float, @@ -23,7 +22,6 @@ coerce_boolean as coerce_boolean, coerce_integer as coerce_integer, file_from_path as file_from_path, - parse_datetime as parse_datetime, strip_not_given as strip_not_given, deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, @@ -32,6 +30,13 @@ maybe_coerce_boolean as maybe_coerce_boolean, maybe_coerce_integer as maybe_coerce_integer, ) +from ._compat import ( + get_args as get_args, + is_union as is_union, + get_origin as get_origin, + is_typeddict as is_typeddict, + is_literal_type as is_literal_type, +) from ._typing import ( is_list_type as is_list_type, is_union_type as is_union_type, @@ -56,3 +61,4 @@ function_has_argument as function_has_argument, assert_signatures_in_sync as assert_signatures_in_sync, ) +from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime diff --git a/src/isaacus/_utils/_compat.py b/src/isaacus/_utils/_compat.py new file mode 100644 index 0000000..dd70323 --- /dev/null +++ b/src/isaacus/_utils/_compat.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import sys +import typing_extensions +from typing import Any, Type, Union, Literal, Optional +from datetime import date, datetime +from typing_extensions import get_args as _get_args, get_origin as _get_origin + +from .._types import StrBytesIntFloat +from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime + +_LITERAL_TYPES = {Literal, typing_extensions.Literal} + + +def get_args(tp: type[Any]) -> tuple[Any, ...]: + return _get_args(tp) + + +def get_origin(tp: type[Any]) -> type[Any] | None: + return _get_origin(tp) + + +def is_union(tp: Optional[Type[Any]]) -> bool: + if sys.version_info < (3, 10): + return tp is Union # type: ignore[comparison-overlap] + else: + import types + + return tp is Union or tp is types.UnionType + + +def is_typeddict(tp: Type[Any]) -> bool: + return typing_extensions.is_typeddict(tp) + + +def is_literal_type(tp: Type[Any]) -> bool: + return get_origin(tp) in _LITERAL_TYPES + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + return _parse_date(value) + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + return _parse_datetime(value) diff --git a/src/isaacus/_utils/_datetime_parse.py b/src/isaacus/_utils/_datetime_parse.py new file mode 100644 index 0000000..7cb9d9e --- /dev/null +++ b/src/isaacus/_utils/_datetime_parse.py @@ -0,0 +1,136 @@ +""" +This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py +without the Pydantic v1 specific errors. +""" + +from __future__ import annotations + +import re +from typing import Dict, Union, Optional +from datetime import date, datetime, timezone, timedelta + +from .._types import StrBytesIntFloat + +date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})" +time_expr = ( + r"(?P\d{1,2}):(?P\d{1,2})" + r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?" + r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$" +) + +date_re = re.compile(f"{date_expr}$") +datetime_re = re.compile(f"{date_expr}[T ]{time_expr}") + + +EPOCH = datetime(1970, 1, 1) +# if greater than this, the number is in ms, if less than or equal it's in seconds +# (in seconds this is 11th October 2603, in ms it's 20th August 1970) +MS_WATERSHED = int(2e10) +# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9 +MAX_NUMBER = int(3e20) + + +def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]: + if isinstance(value, (int, float)): + return value + try: + return float(value) + except ValueError: + return None + except TypeError: + raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None + + +def _from_unix_seconds(seconds: Union[int, float]) -> datetime: + if seconds > MAX_NUMBER: + return datetime.max + elif seconds < -MAX_NUMBER: + return datetime.min + + while abs(seconds) > MS_WATERSHED: + seconds /= 1000 + dt = EPOCH + timedelta(seconds=seconds) + return dt.replace(tzinfo=timezone.utc) + + +def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]: + if value == "Z": + return timezone.utc + elif value is not None: + offset_mins = int(value[-2:]) if len(value) > 3 else 0 + offset = 60 * int(value[1:3]) + offset_mins + if value[0] == "-": + offset = -offset + return timezone(timedelta(minutes=offset)) + else: + return None + + +def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: + """ + Parse a datetime/int/float/string and return a datetime.datetime. + + This function supports time zone offsets. When the input contains one, + the output uses a timezone with a fixed offset from UTC. + + Raise ValueError if the input is well formatted but not a valid datetime. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, datetime): + return value + + number = _get_numeric(value, "datetime") + if number is not None: + return _from_unix_seconds(number) + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + + match = datetime_re.match(value) + if match is None: + raise ValueError("invalid datetime format") + + kw = match.groupdict() + if kw["microsecond"]: + kw["microsecond"] = kw["microsecond"].ljust(6, "0") + + tzinfo = _parse_timezone(kw.pop("tzinfo")) + kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None} + kw_["tzinfo"] = tzinfo + + return datetime(**kw_) # type: ignore + + +def parse_date(value: Union[date, StrBytesIntFloat]) -> date: + """ + Parse a date/int/float/string and return a datetime.date. + + Raise ValueError if the input is well formatted but not a valid date. + Raise ValueError if the input isn't well formatted. + """ + if isinstance(value, date): + if isinstance(value, datetime): + return value.date() + else: + return value + + number = _get_numeric(value, "date") + if number is not None: + return _from_unix_seconds(number).date() + + if isinstance(value, bytes): + value = value.decode() + + assert not isinstance(value, (float, int)) + match = date_re.match(value) + if match is None: + raise ValueError("invalid date format") + + kw = {k: int(v) for k, v in match.groupdict().items()} + + try: + return date(**kw) + except ValueError: + raise ValueError("invalid date format") from None diff --git a/src/isaacus/_utils/_transform.py b/src/isaacus/_utils/_transform.py index f0bcefd..c19124f 100644 --- a/src/isaacus/_utils/_transform.py +++ b/src/isaacus/_utils/_transform.py @@ -19,6 +19,7 @@ is_sequence, ) from .._files import is_base64_file_input +from ._compat import get_origin, is_typeddict from ._typing import ( is_list_type, is_union_type, @@ -29,7 +30,6 @@ is_annotated_type, strip_annotated_type, ) -from .._compat import get_origin, model_dump, is_typeddict _T = TypeVar("_T") @@ -169,6 +169,8 @@ def _transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation @@ -333,6 +335,8 @@ async def _async_transform_recursive( Defaults to the same value as the `annotation` argument. """ + from .._compat import model_dump + if inner_type is None: inner_type = annotation diff --git a/src/isaacus/_utils/_typing.py b/src/isaacus/_utils/_typing.py index 845cd6b..193109f 100644 --- a/src/isaacus/_utils/_typing.py +++ b/src/isaacus/_utils/_typing.py @@ -15,7 +15,7 @@ from ._utils import lru_cache from .._types import InheritsGeneric -from .._compat import is_union as _is_union +from ._compat import is_union as _is_union def is_annotated_type(typ: type) -> bool: diff --git a/src/isaacus/_utils/_utils.py b/src/isaacus/_utils/_utils.py index ea3cf3f..f081859 100644 --- a/src/isaacus/_utils/_utils.py +++ b/src/isaacus/_utils/_utils.py @@ -22,7 +22,6 @@ import sniffio from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike -from .._compat import parse_date as parse_date, parse_datetime as parse_datetime _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) diff --git a/tests/test_models.py b/tests/test_models.py index 17a73a4..42d881c 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -8,7 +8,7 @@ from pydantic import Field from isaacus._utils import PropertyInfo -from isaacus._compat import PYDANTIC_V2, parse_obj, model_dump, model_json +from isaacus._compat import PYDANTIC_V1, parse_obj, model_dump, model_json from isaacus._models import BaseModel, construct_type @@ -294,12 +294,12 @@ class Model(BaseModel): assert cast(bool, m.foo) is True m = Model.construct(foo={"name": 3}) - if PYDANTIC_V2: - assert isinstance(m.foo, Submodel1) - assert m.foo.name == 3 # type: ignore - else: + if PYDANTIC_V1: assert isinstance(m.foo, Submodel2) assert m.foo.name == "3" + else: + assert isinstance(m.foo, Submodel1) + assert m.foo.name == 3 # type: ignore def test_list_of_unions() -> None: @@ -426,10 +426,10 @@ class Model(BaseModel): expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc) - if PYDANTIC_V2: - expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' - else: + if PYDANTIC_V1: expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}' + else: + expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}' model = Model.construct(created_at="2019-12-27T18:11:19.117Z") assert model.created_at == expected @@ -531,7 +531,7 @@ class Model2(BaseModel): assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)} assert m4.to_dict(mode="json") == {"created_at": time_str} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_dict(warnings=False) @@ -556,7 +556,7 @@ class Model(BaseModel): assert m3.model_dump() == {"foo": None} assert m3.model_dump(exclude_none=True) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump(round_trip=True) @@ -580,10 +580,10 @@ class Model(BaseModel): assert json.loads(m.to_json()) == {"FOO": "hello"} assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"} - if PYDANTIC_V2: - assert m.to_json(indent=None) == '{"FOO":"hello"}' - else: + if PYDANTIC_V1: assert m.to_json(indent=None) == '{"FOO": "hello"}' + else: + assert m.to_json(indent=None) == '{"FOO":"hello"}' m2 = Model() assert json.loads(m2.to_json()) == {} @@ -595,7 +595,7 @@ class Model(BaseModel): assert json.loads(m3.to_json()) == {"FOO": None} assert json.loads(m3.to_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"): m.to_json(warnings=False) @@ -622,7 +622,7 @@ class Model(BaseModel): assert json.loads(m3.model_dump_json()) == {"foo": None} assert json.loads(m3.model_dump_json(exclude_none=True)) == {} - if not PYDANTIC_V2: + if PYDANTIC_V1: with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"): m.model_dump_json(round_trip=True) @@ -679,12 +679,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_unknown_variant() -> None: @@ -768,12 +768,12 @@ class B(BaseModel): ) assert isinstance(m, A) assert m.foo_type == "a" - if PYDANTIC_V2: - assert m.data == 100 # type: ignore[comparison-overlap] - else: + if PYDANTIC_V1: # pydantic v1 automatically converts inputs to strings # if the expected type is a str assert m.data == "100" + else: + assert m.data == 100 # type: ignore[comparison-overlap] def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None: @@ -833,7 +833,7 @@ class B(BaseModel): assert UnionType.__discriminator__ is discriminator -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_type_alias_type() -> None: Alias = TypeAliasType("Alias", str) # pyright: ignore @@ -849,7 +849,7 @@ class Model(BaseModel): assert m.union == "bar" -@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") def test_field_named_cls() -> None: class Model(BaseModel): cls: str @@ -936,7 +936,7 @@ class Type2(BaseModel): assert isinstance(model.value, InnerType2) -@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now") +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now") def test_extra_properties() -> None: class Item(BaseModel): prop: int diff --git a/tests/test_transform.py b/tests/test_transform.py index 229b514..5678b12 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -15,7 +15,7 @@ parse_datetime, async_transform as _async_transform, ) -from isaacus._compat import PYDANTIC_V2 +from isaacus._compat import PYDANTIC_V1 from isaacus._models import BaseModel _T = TypeVar("_T") @@ -189,7 +189,7 @@ class DateModel(BaseModel): @pytest.mark.asyncio async def test_iso8601_format(use_async: bool) -> None: dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00") - tz = "Z" if PYDANTIC_V2 else "+00:00" + tz = "+00:00" if PYDANTIC_V1 else "Z" assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap] assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap] @@ -297,11 +297,11 @@ async def test_pydantic_unknown_field(use_async: bool) -> None: @pytest.mark.asyncio async def test_pydantic_mismatched_types(use_async: bool) -> None: model = MyModel.construct(foo=True) - if PYDANTIC_V2: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": True} @@ -309,11 +309,11 @@ async def test_pydantic_mismatched_types(use_async: bool) -> None: @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: + if PYDANTIC_V1: + params = await transform(model, Any, use_async) + else: with pytest.warns(UserWarning): params = await transform(model, Any, use_async) - else: - params = await transform(model, Any, use_async) assert cast(Any, params) == {"foo": {"hello": "world"}} diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py new file mode 100644 index 0000000..ff1ab27 --- /dev/null +++ b/tests/test_utils/test_datetime_parse.py @@ -0,0 +1,110 @@ +""" +Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py +with modifications so it works without pydantic v1 imports. +""" + +from typing import Type, Union +from datetime import date, datetime, timezone, timedelta + +import pytest + +from isaacus._utils import parse_date, parse_datetime + + +def create_tz(minutes: int) -> timezone: + return timezone(timedelta(minutes=minutes)) + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + ("1494012444.883309", date(2017, 5, 5)), + (b"1494012444.883309", date(2017, 5, 5)), + (1_494_012_444.883_309, date(2017, 5, 5)), + ("1494012444", date(2017, 5, 5)), + (1_494_012_444, date(2017, 5, 5)), + (0, date(1970, 1, 1)), + ("2012-04-23", date(2012, 4, 23)), + (b"2012-04-23", date(2012, 4, 23)), + ("2012-4-9", date(2012, 4, 9)), + (date(2012, 4, 9), date(2012, 4, 9)), + (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), + # Invalid inputs + ("x20120423", ValueError), + ("2012-04-56", ValueError), + (19_999_999_999, date(2603, 10, 11)), # just before watershed + (20_000_000_001, date(1970, 8, 20)), # just after watershed + (1_549_316_052, date(2019, 2, 4)), # nowish in s + (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms + (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs + (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns + ("infinity", date(9999, 12, 31)), + ("inf", date(9999, 12, 31)), + (float("inf"), date(9999, 12, 31)), + ("infinity ", date(9999, 12, 31)), + (int("1" + "0" * 100), date(9999, 12, 31)), + (1e1000, date(9999, 12, 31)), + ("-infinity", date(1, 1, 1)), + ("-inf", date(1, 1, 1)), + ("nan", ValueError), + ], +) +def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_date(value) + else: + assert parse_date(value) == result + + +@pytest.mark.parametrize( + "value,result", + [ + # Valid inputs + # values in seconds + ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), + ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + # values in ms + ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), + ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), + (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), + ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)), + ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)), + ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), + ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), + ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), + ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), + ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), + (datetime(2017, 5, 5), datetime(2017, 5, 5)), + (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), + # Invalid inputs + ("x20120423091500", ValueError), + ("2012-04-56T09:15:90", ValueError), + ("2012-04-23T11:05:00-25:00", ValueError), + (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed + (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed + (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s + (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms + (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs + (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns + ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)), + (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)), + (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)), + ("-infinity", datetime(1, 1, 1, 0, 0)), + ("-inf", datetime(1, 1, 1, 0, 0)), + ("nan", ValueError), + ], +) +def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None: + if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance] + with pytest.raises(result): + parse_datetime(value) + else: + assert parse_datetime(value) == result diff --git a/tests/utils.py b/tests/utils.py index 18a607d..f75263f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -19,7 +19,7 @@ is_annotated_type, is_type_alias_type, ) -from isaacus._compat import PYDANTIC_V2, field_outer_type, get_model_fields +from isaacus._compat import PYDANTIC_V1, field_outer_type, get_model_fields from isaacus._models import BaseModel BaseModelT = TypeVar("BaseModelT", bound=BaseModel) @@ -28,12 +28,12 @@ def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool: for name, field in get_model_fields(model).items(): field_value = getattr(value, name) - if PYDANTIC_V2: - allow_none = False - else: + if PYDANTIC_V1: # in v1 nullability was structured differently # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields allow_none = getattr(field, "allow_none", False) + else: + allow_none = False assert_matches_type( field_outer_type(field), From d5732d5e0145763723e8be24cbd8296f9a385264 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 04:16:58 +0000 Subject: [PATCH 13/38] chore(internal): move mypy configurations to `pyproject.toml` file --- mypy.ini | 50 ------------------------------------------------ pyproject.toml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) delete mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 4dd5e1b..0000000 --- a/mypy.ini +++ /dev/null @@ -1,50 +0,0 @@ -[mypy] -pretty = True -show_error_codes = True - -# Exclude _files.py because mypy isn't smart enough to apply -# the correct type narrowing and as this is an internal module -# it's fine to just use Pyright. -# -# We also exclude our `tests` as mypy doesn't always infer -# types correctly and Pyright will still catch any type errors. -exclude = ^(src/isaacus/_files\.py|_dev/.*\.py|tests/.*)$ - -strict_equality = True -implicit_reexport = True -check_untyped_defs = True -no_implicit_optional = True - -warn_return_any = True -warn_unreachable = True -warn_unused_configs = True - -# Turn these options off as it could cause conflicts -# with the Pyright options. -warn_unused_ignores = False -warn_redundant_casts = False - -disallow_any_generics = True -disallow_untyped_defs = True -disallow_untyped_calls = True -disallow_subclassing_any = True -disallow_incomplete_defs = True -disallow_untyped_decorators = True -cache_fine_grained = True - -# By default, mypy reports an error if you assign a value to the result -# of a function call that doesn't return anything. We do this in our test -# cases: -# ``` -# result = ... -# assert result is None -# ``` -# Changing this codegen to make mypy happy would increase complexity -# and would not be worth it. -disable_error_code = func-returns-value,overload-cannot-match - -# https://github.com/python/mypy/issues/12162 -[mypy.overrides] -module = "black.files.*" -ignore_errors = true -ignore_missing_imports = true diff --git a/pyproject.toml b/pyproject.toml index 65e0867..25d7efe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,6 +157,58 @@ reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +[tool.mypy] +pretty = true +show_error_codes = true + +# Exclude _files.py because mypy isn't smart enough to apply +# the correct type narrowing and as this is an internal module +# it's fine to just use Pyright. +# +# We also exclude our `tests` as mypy doesn't always infer +# types correctly and Pyright will still catch any type errors. +exclude = ['src/isaacus/_files.py', '_dev/.*.py', 'tests/.*'] + +strict_equality = true +implicit_reexport = true +check_untyped_defs = true +no_implicit_optional = true + +warn_return_any = true +warn_unreachable = true +warn_unused_configs = true + +# Turn these options off as it could cause conflicts +# with the Pyright options. +warn_unused_ignores = false +warn_redundant_casts = false + +disallow_any_generics = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +cache_fine_grained = true + +# By default, mypy reports an error if you assign a value to the result +# of a function call that doesn't return anything. We do this in our test +# cases: +# ``` +# result = ... +# assert result is None +# ``` +# Changing this codegen to make mypy happy would increase complexity +# and would not be worth it. +disable_error_code = "func-returns-value,overload-cannot-match" + +# https://github.com/python/mypy/issues/12162 +[[tool.mypy.overrides]] +module = "black.files.*" +ignore_errors = true +ignore_missing_imports = true + + [tool.ruff] line-length = 120 output-format = "grouped" From e00ccd0c41c3751eb3fae880223ebb05eae0f154 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Sep 2025 04:50:19 +0000 Subject: [PATCH 14/38] chore(tests): simplify `get_platform` test `nest_asyncio` is archived and broken on some platforms so it's not worth keeping in our test suite. --- pyproject.toml | 1 - requirements-dev.lock | 1 - tests/test_client.py | 53 +++++-------------------------------------- 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 25d7efe..1fbaf80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0", "pytest-xdist>=3.6.1", ] diff --git a/requirements-dev.lock b/requirements-dev.lock index 0832a84..eba0a32 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -75,7 +75,6 @@ multidict==6.4.4 mypy==1.14.1 mypy-extensions==1.0.0 # via mypy -nest-asyncio==1.6.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 diff --git a/tests/test_client.py b/tests/test_client.py index 8924385..dcb7d53 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -6,13 +6,10 @@ import os import sys import json -import time import asyncio import inspect -import subprocess import tracemalloc from typing import Any, Union, cast -from textwrap import dedent from unittest import mock from typing_extensions import Literal @@ -23,14 +20,17 @@ from isaacus import Isaacus, AsyncIsaacus, APIResponseValidationError from isaacus._types import Omit +from isaacus._utils import asyncify from isaacus._models import BaseModel, FinalRequestOptions from isaacus._exceptions import IsaacusError, APIStatusError, APITimeoutError, APIResponseValidationError from isaacus._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + OtherPlatform, DefaultHttpxClient, DefaultAsyncHttpxClient, + get_platform, make_request_options, ) @@ -1669,50 +1669,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" - def test_get_platform(self) -> None: - # A previous implementation of asyncify could leave threads unterminated when - # used with nest_asyncio. - # - # Since nest_asyncio.apply() is global and cannot be un-applied, this - # test is run in a separate process to avoid affecting other tests. - test_code = dedent(""" - import asyncio - import nest_asyncio - import threading - - from isaacus._utils import asyncify - from isaacus._base_client import get_platform - - async def test_main() -> None: - result = await asyncify(get_platform)() - print(result) - for thread in threading.enumerate(): - print(thread.name) - - nest_asyncio.apply() - asyncio.run(test_main()) - """) - with subprocess.Popen( - [sys.executable, "-c", test_code], - text=True, - ) as process: - timeout = 10 # seconds - - start_time = time.monotonic() - while True: - return_code = process.poll() - if return_code is not None: - if return_code != 0: - raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code") - - # success - break - - if time.monotonic() - start_time > timeout: - process.kill() - raise AssertionError("calling get_platform using asyncify resulted in a hung process") - - time.sleep(0.1) + async def test_get_platform(self) -> None: + platform = await asyncify(get_platform)() + assert isinstance(platform, (str, OtherPlatform)) async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly From 68a70578a2e269fa3b2c46e3c29e82ba770090d6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 03:09:30 +0000 Subject: [PATCH 15/38] chore(internal): update pydantic dependency --- requirements-dev.lock | 7 +++++-- requirements.lock | 7 +++++-- src/isaacus/_models.py | 14 ++++++++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index eba0a32..0d47ff5 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -88,9 +88,9 @@ pluggy==1.5.0 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via isaacus -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic pygments==2.18.0 # via rich @@ -126,6 +126,9 @@ typing-extensions==4.12.2 # via pydantic # via pydantic-core # via pyright + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic virtualenv==20.24.5 # via nox yarl==1.20.0 diff --git a/requirements.lock b/requirements.lock index 1416b4e..89e380e 100644 --- a/requirements.lock +++ b/requirements.lock @@ -55,9 +55,9 @@ multidict==6.4.4 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.10.3 +pydantic==2.11.9 # via isaacus -pydantic-core==2.27.1 +pydantic-core==2.33.2 # via pydantic sniffio==1.3.0 # via anyio @@ -68,5 +68,8 @@ typing-extensions==4.12.2 # via multidict # via pydantic # via pydantic-core + # via typing-inspection +typing-inspection==0.4.1 + # via pydantic yarl==1.20.0 # via aiohttp diff --git a/src/isaacus/_models.py b/src/isaacus/_models.py index 3a6017e..6a3cd1d 100644 --- a/src/isaacus/_models.py +++ b/src/isaacus/_models.py @@ -256,7 +256,7 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, @@ -264,6 +264,7 @@ def model_dump( warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, serialize_as_any: bool = False, + fallback: Callable[[Any], Any] | None = None, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -295,10 +296,12 @@ def model_dump( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, @@ -313,13 +316,14 @@ def model_dump_json( indent: int | None = None, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool = False, + by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, context: dict[str, Any] | None = None, + fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json @@ -348,11 +352,13 @@ def model_dump_json( raise ValueError("context is only supported in Pydantic v2") if serialize_as_any != False: raise ValueError("serialize_as_any is only supported in Pydantic v2") + if fallback is not None: + raise ValueError("fallback is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, exclude=exclude, - by_alias=by_alias, + by_alias=by_alias if by_alias is not None else False, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, From 38d13e0514b001d1a34446b881783d559e246865 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 02:14:00 +0000 Subject: [PATCH 16/38] chore(types): change optional parameter type from NotGiven to Omit --- src/isaacus/__init__.py | 4 ++- src/isaacus/_base_client.py | 12 ++++---- src/isaacus/_client.py | 16 +++++----- src/isaacus/_qs.py | 14 ++++----- src/isaacus/_types.py | 29 ++++++++++++------- src/isaacus/_utils/_transform.py | 4 +-- src/isaacus/_utils/_utils.py | 8 ++--- .../resources/classifications/universal.py | 18 ++++++------ src/isaacus/resources/extractions/qa.py | 18 ++++++------ src/isaacus/resources/rerankings.py | 22 +++++++------- tests/test_transform.py | 11 +++++-- 11 files changed, 86 insertions(+), 70 deletions(-) diff --git a/src/isaacus/__init__.py b/src/isaacus/__init__.py index 6a1c403..73067d5 100644 --- a/src/isaacus/__init__.py +++ b/src/isaacus/__init__.py @@ -3,7 +3,7 @@ import typing as _t from . import types -from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path from ._client import Client, Stream, Isaacus, Timeout, Transport, AsyncClient, AsyncStream, AsyncIsaacus, RequestOptions from ._models import BaseModel @@ -38,7 +38,9 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "not_given", "Omit", + "omit", "IsaacusError", "APIError", "APIStatusError", diff --git a/src/isaacus/_base_client.py b/src/isaacus/_base_client.py index d6d847b..9c3c7fc 100644 --- a/src/isaacus/_base_client.py +++ b/src/isaacus/_base_client.py @@ -42,7 +42,6 @@ from ._qs import Querystring from ._files import to_httpx_files, async_to_httpx_files from ._types import ( - NOT_GIVEN, Body, Omit, Query, @@ -57,6 +56,7 @@ RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, + not_given, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping from ._compat import PYDANTIC_V1, model_copy, model_dump @@ -570,7 +570,7 @@ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalReques # we internally support defining a temporary header to override the # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` # see _response.py for implementation details - override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given) if is_given(override_cast_to): options.headers = headers return cast(Type[ResponseT], override_cast_to) @@ -800,7 +800,7 @@ def __init__( version: str, base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1331,7 +1331,7 @@ def __init__( base_url: str | URL, _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, @@ -1793,8 +1793,8 @@ def make_request_options( extra_query: Query | None = None, extra_body: Body | None = None, idempotency_key: str | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - post_parser: PostParser | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + post_parser: PostParser | NotGiven = not_given, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} diff --git a/src/isaacus/_client.py b/src/isaacus/_client.py index 35105e6..7bbac58 100644 --- a/src/isaacus/_client.py +++ b/src/isaacus/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Union, Mapping +from typing import Any, Mapping from typing_extensions import Self, override import httpx @@ -11,13 +11,13 @@ from . import _exceptions from ._qs import Querystring from ._types import ( - NOT_GIVEN, Omit, Timeout, NotGiven, Transport, ProxiesTypes, RequestOptions, + not_given, ) from ._utils import is_given, get_async_library from ._version import __version__ @@ -50,7 +50,7 @@ def __init__( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -127,9 +127,9 @@ def copy( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -222,7 +222,7 @@ def __init__( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, @@ -299,9 +299,9 @@ def copy( *, api_key: str | None = None, base_url: str | httpx.URL | None = None, - timeout: float | Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, - max_retries: int | NotGiven = NOT_GIVEN, + max_retries: int | NotGiven = not_given, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, default_query: Mapping[str, object] | None = None, diff --git a/src/isaacus/_qs.py b/src/isaacus/_qs.py index 274320c..ada6fd3 100644 --- a/src/isaacus/_qs.py +++ b/src/isaacus/_qs.py @@ -4,7 +4,7 @@ from urllib.parse import parse_qs, urlencode from typing_extensions import Literal, get_args -from ._types import NOT_GIVEN, NotGiven, NotGivenOr +from ._types import NotGiven, not_given from ._utils import flatten _T = TypeVar("_T") @@ -41,8 +41,8 @@ def stringify( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> str: return urlencode( self.stringify_items( @@ -56,8 +56,8 @@ def stringify_items( self, params: Params, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> list[tuple[str, str]]: opts = Options( qs=self, @@ -143,8 +143,8 @@ def __init__( self, qs: Querystring = _qs, *, - array_format: NotGivenOr[ArrayFormat] = NOT_GIVEN, - nested_format: NotGivenOr[NestedFormat] = NOT_GIVEN, + array_format: ArrayFormat | NotGiven = not_given, + nested_format: NestedFormat | NotGiven = not_given, ) -> None: self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format diff --git a/src/isaacus/_types.py b/src/isaacus/_types.py index 5b999d6..905c413 100644 --- a/src/isaacus/_types.py +++ b/src/isaacus/_types.py @@ -117,18 +117,21 @@ class RequestOptions(TypedDict, total=False): # Sentinel class used until PEP 0661 is accepted class NotGiven: """ - A sentinel singleton class used to distinguish omitted keyword arguments - from those passed in with the value None (which may have different behavior). + For parameters with a meaningful None value, we need to distinguish between + the user explicitly passing None, and the user not passing the parameter at + all. + + User code shouldn't need to use not_given directly. For example: ```py - def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ... + def create(timeout: Timeout | None | NotGiven = not_given): ... - get(timeout=1) # 1s timeout - get(timeout=None) # No timeout - get() # Default timeout behavior, which may not be statically known at the method definition. + create(timeout=1) # 1s timeout + create(timeout=None) # No timeout + create() # Default timeout behavior ``` """ @@ -140,13 +143,14 @@ def __repr__(self) -> str: return "NOT_GIVEN" -NotGivenOr = Union[_T, NotGiven] +not_given = NotGiven() +# for backwards compatibility: NOT_GIVEN = NotGiven() class Omit: - """In certain situations you need to be able to represent a case where a default value has - to be explicitly removed and `None` is not an appropriate substitute, for example: + """ + To explicitly omit something from being sent in a request, use `omit`. ```py # as the default `Content-Type` header is `application/json` that will be sent @@ -156,8 +160,8 @@ class Omit: # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983' client.post(..., headers={"Content-Type": "multipart/form-data"}) - # instead you can remove the default `application/json` header by passing Omit - client.post(..., headers={"Content-Type": Omit()}) + # instead you can remove the default `application/json` header by passing omit + client.post(..., headers={"Content-Type": omit}) ``` """ @@ -165,6 +169,9 @@ def __bool__(self) -> Literal[False]: return False +omit = Omit() + + @runtime_checkable class ModelBuilderProtocol(Protocol): @classmethod diff --git a/src/isaacus/_utils/_transform.py b/src/isaacus/_utils/_transform.py index c19124f..5207549 100644 --- a/src/isaacus/_utils/_transform.py +++ b/src/isaacus/_utils/_transform.py @@ -268,7 +268,7 @@ def _transform_typeddict( 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 + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue @@ -434,7 +434,7 @@ async def _async_transform_typeddict( 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 + # we don't need to include omitted values here as they'll # be stripped out before the request is sent anyway continue diff --git a/src/isaacus/_utils/_utils.py b/src/isaacus/_utils/_utils.py index f081859..50d5926 100644 --- a/src/isaacus/_utils/_utils.py +++ b/src/isaacus/_utils/_utils.py @@ -21,7 +21,7 @@ import sniffio -from .._types import NotGiven, FileTypes, NotGivenOr, HeadersLike +from .._types import Omit, NotGiven, FileTypes, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -63,7 +63,7 @@ def _extract_items( try: key = path[index] except IndexError: - if isinstance(obj, NotGiven): + if not is_given(obj): # no value was provided - we can safely ignore return [] @@ -126,8 +126,8 @@ def _extract_items( return [] -def is_given(obj: NotGivenOr[_T]) -> TypeGuard[_T]: - return not isinstance(obj, NotGiven) +def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: + return not isinstance(obj, NotGiven) and not isinstance(obj, Omit) # Type safe methods for narrowing types with TypeVars. diff --git a/src/isaacus/resources/classifications/universal.py b/src/isaacus/resources/classifications/universal.py index eab1539..e700bbd 100644 --- a/src/isaacus/resources/classifications/universal.py +++ b/src/isaacus/resources/classifications/universal.py @@ -7,7 +7,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -50,15 +50,15 @@ def create( model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, texts: SequenceNotStr[str], - chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, - is_iql: bool | NotGiven = NOT_GIVEN, - scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, + chunking_options: Optional[universal_create_params.ChunkingOptions] | Omit = omit, + is_iql: bool | Omit = omit, + scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | 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. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UniversalClassification: """ Classify the relevance of legal documents to a query with an Isaacus universal @@ -151,15 +151,15 @@ async def create( model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, texts: SequenceNotStr[str], - chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, - is_iql: bool | NotGiven = NOT_GIVEN, - scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, + chunking_options: Optional[universal_create_params.ChunkingOptions] | Omit = omit, + is_iql: bool | Omit = omit, + scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | 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. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> UniversalClassification: """ Classify the relevance of legal documents to a query with an Isaacus universal diff --git a/src/isaacus/resources/extractions/qa.py b/src/isaacus/resources/extractions/qa.py index 1fd5652..303f0d1 100644 --- a/src/isaacus/resources/extractions/qa.py +++ b/src/isaacus/resources/extractions/qa.py @@ -7,7 +7,7 @@ import httpx -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr +from ..._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -50,15 +50,15 @@ def create( model: Literal["kanon-answer-extractor", "kanon-answer-extractor-mini"], query: str, texts: SequenceNotStr[str], - chunking_options: Optional[qa_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, - ignore_inextractability: bool | NotGiven = NOT_GIVEN, - top_k: int | NotGiven = NOT_GIVEN, + chunking_options: Optional[qa_create_params.ChunkingOptions] | Omit = omit, + ignore_inextractability: bool | Omit = omit, + top_k: int | 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. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AnswerExtraction: """ Extract answers to questions from legal documents with an Isaacus legal AI @@ -149,15 +149,15 @@ async def create( model: Literal["kanon-answer-extractor", "kanon-answer-extractor-mini"], query: str, texts: SequenceNotStr[str], - chunking_options: Optional[qa_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, - ignore_inextractability: bool | NotGiven = NOT_GIVEN, - top_k: int | NotGiven = NOT_GIVEN, + chunking_options: Optional[qa_create_params.ChunkingOptions] | Omit = omit, + ignore_inextractability: bool | Omit = omit, + top_k: int | 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. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AnswerExtraction: """ Extract answers to questions from legal documents with an Isaacus legal AI diff --git a/src/isaacus/resources/rerankings.py b/src/isaacus/resources/rerankings.py index bd8de95..e839012 100644 --- a/src/isaacus/resources/rerankings.py +++ b/src/isaacus/resources/rerankings.py @@ -8,7 +8,7 @@ import httpx from ..types import reranking_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, SequenceNotStr +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -50,16 +50,16 @@ def create( model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, texts: SequenceNotStr[str], - chunking_options: Optional[reranking_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, - is_iql: bool | NotGiven = NOT_GIVEN, - scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, - top_n: Optional[int] | NotGiven = NOT_GIVEN, + chunking_options: Optional[reranking_create_params.ChunkingOptions] | Omit = omit, + is_iql: bool | Omit = omit, + scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | Omit = omit, + top_n: Optional[int] | 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. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Reranking: """ Rerank legal documents by their relevance to a query with an Isaacus legal AI @@ -161,16 +161,16 @@ async def create( model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, texts: SequenceNotStr[str], - chunking_options: Optional[reranking_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, - is_iql: bool | NotGiven = NOT_GIVEN, - scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | NotGiven = NOT_GIVEN, - top_n: Optional[int] | NotGiven = NOT_GIVEN, + chunking_options: Optional[reranking_create_params.ChunkingOptions] | Omit = omit, + is_iql: bool | Omit = omit, + scoring_method: Literal["auto", "chunk_max", "chunk_avg", "chunk_min"] | Omit = omit, + top_n: Optional[int] | 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. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Reranking: """ Rerank legal documents by their relevance to a query with an Isaacus legal AI diff --git a/tests/test_transform.py b/tests/test_transform.py index 5678b12..6e96e84 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from isaacus._types import NOT_GIVEN, Base64FileInput +from isaacus._types import Base64FileInput, omit, not_given from isaacus._utils import ( PropertyInfo, transform as _transform, @@ -450,4 +450,11 @@ async def test_transform_skipping(use_async: bool) -> None: @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) == {} + assert await transform({"foo_bar": not_given}, Foo1, use_async) == {} + + +@parametrize +@pytest.mark.asyncio +async def test_strips_omit(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": omit}, Foo1, use_async) == {} From 57b055ed56fdcc58b4663e4ddad32afac25e7ec1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 03:33:03 +0000 Subject: [PATCH 17/38] chore: do not install brew dependencies in ./scripts/bootstrap by default --- scripts/bootstrap | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index e84fe62..b430fee 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if ! command -v rye >/dev/null 2>&1 && [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi From 35b03bdbf4ceaccd00102e23d639a01d5bea136a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 02:59:43 +0000 Subject: [PATCH 18/38] chore: improve example values --- tests/api_resources/classifications/test_universal.py | 4 ++-- tests/api_resources/extractions/test_qa.py | 4 ++-- tests/api_resources/test_rerankings.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/api_resources/classifications/test_universal.py b/tests/api_resources/classifications/test_universal.py index ec481a9..892e6b4 100644 --- a/tests/api_resources/classifications/test_universal.py +++ b/tests/api_resources/classifications/test_universal.py @@ -36,7 +36,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: texts=["I agree not to tell anyone about the document."], chunking_options={ "overlap_ratio": 0.1, - "overlap_tokens": 0, + "overlap_tokens": 10, "size": 512, }, is_iql=True, @@ -99,7 +99,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - texts=["I agree not to tell anyone about the document."], chunking_options={ "overlap_ratio": 0.1, - "overlap_tokens": 0, + "overlap_tokens": 10, "size": 512, }, is_iql=True, diff --git a/tests/api_resources/extractions/test_qa.py b/tests/api_resources/extractions/test_qa.py index 1c389a1..798f00d 100644 --- a/tests/api_resources/extractions/test_qa.py +++ b/tests/api_resources/extractions/test_qa.py @@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: ], chunking_options={ "overlap_ratio": 0.1, - "overlap_tokens": 0, + "overlap_tokens": 10, "size": 512, }, ignore_inextractability=False, @@ -111,7 +111,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - ], chunking_options={ "overlap_ratio": 0.1, - "overlap_tokens": 0, + "overlap_tokens": 10, "size": 512, }, ignore_inextractability=False, diff --git a/tests/api_resources/test_rerankings.py b/tests/api_resources/test_rerankings.py index 845def9..38bfa44 100644 --- a/tests/api_resources/test_rerankings.py +++ b/tests/api_resources/test_rerankings.py @@ -48,7 +48,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: ], chunking_options={ "overlap_ratio": 0.1, - "overlap_tokens": 0, + "overlap_tokens": 10, "size": 512, }, is_iql=False, @@ -136,7 +136,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - ], chunking_options={ "overlap_ratio": 0.1, - "overlap_tokens": 0, + "overlap_tokens": 10, "size": 512, }, is_iql=False, From 491dbdcd82984d099b8ee11e94894ad450b2424d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 02:16:10 +0000 Subject: [PATCH 19/38] chore: remove custom code --- src/isaacus/_base_client.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/isaacus/_base_client.py b/src/isaacus/_base_client.py index 9c3c7fc..56e03dc 100644 --- a/src/isaacus/_base_client.py +++ b/src/isaacus/_base_client.py @@ -119,6 +119,7 @@ class PageInfo: url: URL | NotGiven params: Query | NotGiven + json: Body | NotGiven @overload def __init__( @@ -134,19 +135,30 @@ def __init__( params: Query, ) -> None: ... + @overload + def __init__( + self, + *, + json: Body, + ) -> None: ... + def __init__( self, *, - url: URL | NotGiven = NOT_GIVEN, - params: Query | NotGiven = NOT_GIVEN, + url: URL | NotGiven = not_given, + json: Body | NotGiven = not_given, + params: Query | NotGiven = not_given, ) -> None: self.url = url + self.json = json self.params = params @override def __repr__(self) -> str: if self.url: return f"{self.__class__.__name__}(url={self.url})" + if self.json: + return f"{self.__class__.__name__}(json={self.json})" return f"{self.__class__.__name__}(params={self.params})" @@ -195,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions: options.url = str(url) return options + if not isinstance(info.json, NotGiven): + if not is_mapping(info.json): + raise TypeError("Pagination is only supported with mappings") + + if not options.json_data: + options.json_data = {**info.json} + else: + if not is_mapping(options.json_data): + raise TypeError("Pagination is only supported with mappings") + + options.json_data = {**options.json_data, **info.json} + return options + raise ValueError("Unexpected PageInfo state") From 88190d6d33c8d5e3cf59dfd3c488b5ae9abec93b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 05:59:35 +0000 Subject: [PATCH 20/38] feat(api): added embedding endpoint --- .stats.yml | 4 ++-- pyproject.toml | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b8132f0..995272b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-a0aa3bcfef3af964f7172cecc6e969193a4ca96b26f8c47e7f50d852b13ef356.yml -openapi_spec_hash: e243aed52e8a3c6dad6254c57408fdc4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-738b58fedafe09e1b33bba831e566d19b1d102b82b1a8b6c73c9690703a618ed.yml +openapi_spec_hash: c0fe9067a60c8a311e3bf6ae9d9fa42f config_hash: bfe30148ec88e8bbbf4a348a9fdfc00a diff --git a/pyproject.toml b/pyproject.toml index 1fbaf80..1c54c43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -224,6 +224,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -246,6 +248,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" From 6d99681b9e233adf500fda4a6c22a3fd49574444 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:00:12 +0000 Subject: [PATCH 21/38] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 995272b..b21230c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-738b58fedafe09e1b33bba831e566d19b1d102b82b1a8b6c73c9690703a618ed.yml -openapi_spec_hash: c0fe9067a60c8a311e3bf6ae9d9fa42f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-9630e7345084c0f101952b87feb7d3ad367ded37028cd56619cfad3bfc23e571.yml +openapi_spec_hash: 6262cd9343d6c42f113b8f2272d2d5ab config_hash: bfe30148ec88e8bbbf4a348a9fdfc00a From f4e26b5cd3bba1b47fa7dffe7a24a2724d33fb7e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:00:47 +0000 Subject: [PATCH 22/38] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b21230c..6e3ecfc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-9630e7345084c0f101952b87feb7d3ad367ded37028cd56619cfad3bfc23e571.yml -openapi_spec_hash: 6262cd9343d6c42f113b8f2272d2d5ab +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-e3bb313424a4b8048fcc6c06626630b9f3954ada00e47cb082e02355cf2673c9.yml +openapi_spec_hash: 779220bf4724a16c18a26a88ff6ffbf8 config_hash: bfe30148ec88e8bbbf4a348a9fdfc00a From 1cbe275a87e2ebd0598ee90b61b7b702b603d5b7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:01:20 +0000 Subject: [PATCH 23/38] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6e3ecfc..ec5f7a2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-e3bb313424a4b8048fcc6c06626630b9f3954ada00e47cb082e02355cf2673c9.yml -openapi_spec_hash: 779220bf4724a16c18a26a88ff6ffbf8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-f4b3340872e913a3cc1dfd27b913d7ff9034881e093e57565c3e4e9f70128c6c.yml +openapi_spec_hash: e8d746a5f9ad96beb227dfad222ca9ef config_hash: bfe30148ec88e8bbbf4a348a9fdfc00a From 920ae0b65f2362ac098f8b94979b1e821f5143d8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:01:57 +0000 Subject: [PATCH 24/38] feat(sdk): add embeddings endpoint --- .stats.yml | 4 +- api.md | 12 + src/isaacus/_client.py | 10 +- src/isaacus/resources/__init__.py | 14 ++ src/isaacus/resources/embeddings.py | 246 +++++++++++++++++++ src/isaacus/types/__init__.py | 2 + src/isaacus/types/embedding.py | 20 ++ src/isaacus/types/embedding_create_params.py | 49 ++++ tests/api_resources/test_embeddings.py | 122 +++++++++ 9 files changed, 476 insertions(+), 3 deletions(-) create mode 100644 src/isaacus/resources/embeddings.py create mode 100644 src/isaacus/types/embedding.py create mode 100644 src/isaacus/types/embedding_create_params.py create mode 100644 tests/api_resources/test_embeddings.py diff --git a/.stats.yml b/.stats.yml index ec5f7a2..caea7e9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 3 +configured_endpoints: 4 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-f4b3340872e913a3cc1dfd27b913d7ff9034881e093e57565c3e4e9f70128c6c.yml openapi_spec_hash: e8d746a5f9ad96beb227dfad222ca9ef -config_hash: bfe30148ec88e8bbbf4a348a9fdfc00a +config_hash: 886b5eef0dbd90b8e6686e987a07b816 diff --git a/api.md b/api.md index a3a6143..6a1d5c7 100644 --- a/api.md +++ b/api.md @@ -1,3 +1,15 @@ +# Embeddings + +Types: + +```python +from isaacus.types import Embedding +``` + +Methods: + +- client.embeddings.create(\*\*params) -> Embedding + # Classifications ## Universal diff --git a/src/isaacus/_client.py b/src/isaacus/_client.py index 7bbac58..990b9e6 100644 --- a/src/isaacus/_client.py +++ b/src/isaacus/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import rerankings +from .resources import embeddings, rerankings from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import IsaacusError, APIStatusError from ._base_client import ( @@ -36,6 +36,7 @@ class Isaacus(SyncAPIClient): + embeddings: embeddings.EmbeddingsResource classifications: classifications.ClassificationsResource rerankings: rerankings.RerankingsResource extractions: extractions.ExtractionsResource @@ -96,6 +97,7 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self.embeddings = embeddings.EmbeddingsResource(self) self.classifications = classifications.ClassificationsResource(self) self.rerankings = rerankings.RerankingsResource(self) self.extractions = extractions.ExtractionsResource(self) @@ -208,6 +210,7 @@ def _make_status_error( class AsyncIsaacus(AsyncAPIClient): + embeddings: embeddings.AsyncEmbeddingsResource classifications: classifications.AsyncClassificationsResource rerankings: rerankings.AsyncRerankingsResource extractions: extractions.AsyncExtractionsResource @@ -268,6 +271,7 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self.embeddings = embeddings.AsyncEmbeddingsResource(self) self.classifications = classifications.AsyncClassificationsResource(self) self.rerankings = rerankings.AsyncRerankingsResource(self) self.extractions = extractions.AsyncExtractionsResource(self) @@ -381,6 +385,7 @@ def _make_status_error( class IsaacusWithRawResponse: def __init__(self, client: Isaacus) -> None: + self.embeddings = embeddings.EmbeddingsResourceWithRawResponse(client.embeddings) self.classifications = classifications.ClassificationsResourceWithRawResponse(client.classifications) self.rerankings = rerankings.RerankingsResourceWithRawResponse(client.rerankings) self.extractions = extractions.ExtractionsResourceWithRawResponse(client.extractions) @@ -388,6 +393,7 @@ def __init__(self, client: Isaacus) -> None: class AsyncIsaacusWithRawResponse: def __init__(self, client: AsyncIsaacus) -> None: + self.embeddings = embeddings.AsyncEmbeddingsResourceWithRawResponse(client.embeddings) self.classifications = classifications.AsyncClassificationsResourceWithRawResponse(client.classifications) self.rerankings = rerankings.AsyncRerankingsResourceWithRawResponse(client.rerankings) self.extractions = extractions.AsyncExtractionsResourceWithRawResponse(client.extractions) @@ -395,6 +401,7 @@ def __init__(self, client: AsyncIsaacus) -> None: class IsaacusWithStreamedResponse: def __init__(self, client: Isaacus) -> None: + self.embeddings = embeddings.EmbeddingsResourceWithStreamingResponse(client.embeddings) self.classifications = classifications.ClassificationsResourceWithStreamingResponse(client.classifications) self.rerankings = rerankings.RerankingsResourceWithStreamingResponse(client.rerankings) self.extractions = extractions.ExtractionsResourceWithStreamingResponse(client.extractions) @@ -402,6 +409,7 @@ def __init__(self, client: Isaacus) -> None: class AsyncIsaacusWithStreamedResponse: def __init__(self, client: AsyncIsaacus) -> None: + self.embeddings = embeddings.AsyncEmbeddingsResourceWithStreamingResponse(client.embeddings) self.classifications = classifications.AsyncClassificationsResourceWithStreamingResponse(client.classifications) self.rerankings = rerankings.AsyncRerankingsResourceWithStreamingResponse(client.rerankings) self.extractions = extractions.AsyncExtractionsResourceWithStreamingResponse(client.extractions) diff --git a/src/isaacus/resources/__init__.py b/src/isaacus/resources/__init__.py index e55176e..3c62ff4 100644 --- a/src/isaacus/resources/__init__.py +++ b/src/isaacus/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .embeddings import ( + EmbeddingsResource, + AsyncEmbeddingsResource, + EmbeddingsResourceWithRawResponse, + AsyncEmbeddingsResourceWithRawResponse, + EmbeddingsResourceWithStreamingResponse, + AsyncEmbeddingsResourceWithStreamingResponse, +) from .rerankings import ( RerankingsResource, AsyncRerankingsResource, @@ -26,6 +34,12 @@ ) __all__ = [ + "EmbeddingsResource", + "AsyncEmbeddingsResource", + "EmbeddingsResourceWithRawResponse", + "AsyncEmbeddingsResourceWithRawResponse", + "EmbeddingsResourceWithStreamingResponse", + "AsyncEmbeddingsResourceWithStreamingResponse", "ClassificationsResource", "AsyncClassificationsResource", "ClassificationsResourceWithRawResponse", diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py new file mode 100644 index 0000000..5179230 --- /dev/null +++ b/src/isaacus/resources/embeddings.py @@ -0,0 +1,246 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal + +import httpx + +from ..types import embedding_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.embedding import Embedding + +__all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] + + +class EmbeddingsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EmbeddingsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#accessing-raw-response-data-eg-headers + """ + return EmbeddingsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EmbeddingsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#with_streaming_response + """ + return EmbeddingsResourceWithStreamingResponse(self) + + def create( + self, + *, + model: Literal["kanon-2-embedder"], + texts: Union[SequenceNotStr[str], str], + dimensions: Optional[int] | Omit = omit, + overflow_strategy: Optional[Literal["drop_end"]] | Omit = omit, + task: Optional[Literal["retrieval/query", "retrieval/document"]] | 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Embedding: + """ + Embed legal texts with an Isaacus legal AI embedder. + + Args: + model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + embedding. + + texts: The text or array of texts to embed. + + Each text must contain at least one non-whitespace character. + + No more than 1,000 texts can be embedded in a single request. + + dimensions: A whole number greater than or equal to 1. + + overflow_strategy: The strategy to employ when content exceeds the model's maximum input length. + + `drop_end`, which is the default setting, drops tokens from the end of the + content exceeding the limit. + + If `null`, an error will be raised if any content exceeds the model's maximum + input length. + + task: The task the embeddings will be used for. + + `retrieval/query` is meant for queries and statements, and `retrieval/document` + is meant for anything to be retrieved using query embeddings. + + If `null`, which is the default setting, embeddings will not be optimized for + any particular task. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/embeddings", + body=maybe_transform( + { + "model": model, + "texts": texts, + "dimensions": dimensions, + "overflow_strategy": overflow_strategy, + "task": task, + }, + embedding_create_params.EmbeddingCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Embedding, + ) + + +class AsyncEmbeddingsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEmbeddingsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#accessing-raw-response-data-eg-headers + """ + return AsyncEmbeddingsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEmbeddingsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#with_streaming_response + """ + return AsyncEmbeddingsResourceWithStreamingResponse(self) + + async def create( + self, + *, + model: Literal["kanon-2-embedder"], + texts: Union[SequenceNotStr[str], str], + dimensions: Optional[int] | Omit = omit, + overflow_strategy: Optional[Literal["drop_end"]] | Omit = omit, + task: Optional[Literal["retrieval/query", "retrieval/document"]] | 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Embedding: + """ + Embed legal texts with an Isaacus legal AI embedder. + + Args: + model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + embedding. + + texts: The text or array of texts to embed. + + Each text must contain at least one non-whitespace character. + + No more than 1,000 texts can be embedded in a single request. + + dimensions: A whole number greater than or equal to 1. + + overflow_strategy: The strategy to employ when content exceeds the model's maximum input length. + + `drop_end`, which is the default setting, drops tokens from the end of the + content exceeding the limit. + + If `null`, an error will be raised if any content exceeds the model's maximum + input length. + + task: The task the embeddings will be used for. + + `retrieval/query` is meant for queries and statements, and `retrieval/document` + is meant for anything to be retrieved using query embeddings. + + If `null`, which is the default setting, embeddings will not be optimized for + any particular task. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/embeddings", + body=await async_maybe_transform( + { + "model": model, + "texts": texts, + "dimensions": dimensions, + "overflow_strategy": overflow_strategy, + "task": task, + }, + embedding_create_params.EmbeddingCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Embedding, + ) + + +class EmbeddingsResourceWithRawResponse: + def __init__(self, embeddings: EmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = to_raw_response_wrapper( + embeddings.create, + ) + + +class AsyncEmbeddingsResourceWithRawResponse: + def __init__(self, embeddings: AsyncEmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = async_to_raw_response_wrapper( + embeddings.create, + ) + + +class EmbeddingsResourceWithStreamingResponse: + def __init__(self, embeddings: EmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = to_streamed_response_wrapper( + embeddings.create, + ) + + +class AsyncEmbeddingsResourceWithStreamingResponse: + def __init__(self, embeddings: AsyncEmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = async_to_streamed_response_wrapper( + embeddings.create, + ) diff --git a/src/isaacus/types/__init__.py b/src/isaacus/types/__init__.py index e5c4540..c3b6053 100644 --- a/src/isaacus/types/__init__.py +++ b/src/isaacus/types/__init__.py @@ -2,5 +2,7 @@ from __future__ import annotations +from .embedding import Embedding as Embedding from .reranking import Reranking as Reranking +from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .reranking_create_params import RerankingCreateParams as RerankingCreateParams diff --git a/src/isaacus/types/embedding.py b/src/isaacus/types/embedding.py new file mode 100644 index 0000000..ddc8bec --- /dev/null +++ b/src/isaacus/types/embedding.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel + +__all__ = ["Embedding", "Usage"] + + +class Usage(BaseModel): + input_tokens: int + """The number of tokens inputted to the model.""" + + +class Embedding(BaseModel): + embeddings: List[Embedding] + """The embeddings of the inputs.""" + + usage: Usage + """Statistics about the usage of resources in the process of embedding the inputs.""" diff --git a/src/isaacus/types/embedding_create_params.py b/src/isaacus/types/embedding_create_params.py new file mode 100644 index 0000000..3600291 --- /dev/null +++ b/src/isaacus/types/embedding_create_params.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["EmbeddingCreateParams"] + + +class EmbeddingCreateParams(TypedDict, total=False): + model: Required[Literal["kanon-2-embedder"]] + """ + The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + embedding. + """ + + texts: Required[Union[SequenceNotStr[str], str]] + """The text or array of texts to embed. + + Each text must contain at least one non-whitespace character. + + No more than 1,000 texts can be embedded in a single request. + """ + + dimensions: Optional[int] + """A whole number greater than or equal to 1.""" + + overflow_strategy: Optional[Literal["drop_end"]] + """The strategy to employ when content exceeds the model's maximum input length. + + `drop_end`, which is the default setting, drops tokens from the end of the + content exceeding the limit. + + If `null`, an error will be raised if any content exceeds the model's maximum + input length. + """ + + task: Optional[Literal["retrieval/query", "retrieval/document"]] + """The task the embeddings will be used for. + + `retrieval/query` is meant for queries and statements, and `retrieval/document` + is meant for anything to be retrieved using query embeddings. + + If `null`, which is the default setting, embeddings will not be optimized for + any particular task. + """ diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py new file mode 100644 index 0000000..45cfc6a --- /dev/null +++ b/tests/api_resources/test_embeddings.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from isaacus import Isaacus, AsyncIsaacus +from tests.utils import assert_matches_type +from isaacus.types import Embedding + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEmbeddings: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Isaacus) -> None: + embedding = client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Isaacus) -> None: + embedding = client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + dimensions=1, + overflow_strategy="drop_end", + task="retrieval/query", + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Isaacus) -> None: + response = client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + embedding = response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Isaacus) -> None: + with client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + embedding = response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncEmbeddings: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncIsaacus) -> None: + embedding = await async_client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: + embedding = await async_client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + dimensions=1, + overflow_strategy="drop_end", + task="retrieval/query", + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: + response = await async_client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + embedding = await response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: + async with async_client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + embedding = await response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + assert cast(Any, response.is_closed) is True From 204a05d7b1504901766db3c0d0d8ea47a22a16ed Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 06:17:01 +0000 Subject: [PATCH 25/38] feat(api): rename embedding -> embeddings --- .stats.yml | 4 ++-- api.md | 4 ++-- src/isaacus/resources/embeddings.py | 14 +++++++------- src/isaacus/types/__init__.py | 2 +- src/isaacus/types/embedding_create_params.py | 2 +- ...bedding.py => embedding_create_response.py} | 15 +++++++++++++-- tests/api_resources/test_embeddings.py | 18 +++++++++--------- 7 files changed, 35 insertions(+), 24 deletions(-) rename src/isaacus/types/{embedding.py => embedding_create_response.py} (53%) diff --git a/.stats.yml b/.stats.yml index caea7e9..1cf1012 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-f4b3340872e913a3cc1dfd27b913d7ff9034881e093e57565c3e4e9f70128c6c.yml -openapi_spec_hash: e8d746a5f9ad96beb227dfad222ca9ef +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-bc90df05434e3f9e249881744c600bce6e9d04f43d05c1fafa7133d02114f393.yml +openapi_spec_hash: 2205376b167519bd09d2f95741e6141c config_hash: 886b5eef0dbd90b8e6686e987a07b816 diff --git a/api.md b/api.md index 6a1d5c7..e61008c 100644 --- a/api.md +++ b/api.md @@ -3,12 +3,12 @@ Types: ```python -from isaacus.types import Embedding +from isaacus.types import EmbeddingCreateResponse ``` Methods: -- client.embeddings.create(\*\*params) -> Embedding +- client.embeddings.create(\*\*params) -> EmbeddingCreateResponse # Classifications diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py index 5179230..da0b58a 100644 --- a/src/isaacus/resources/embeddings.py +++ b/src/isaacus/resources/embeddings.py @@ -19,7 +19,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.embedding import Embedding +from ..types.embedding_create_response import EmbeddingCreateResponse __all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] @@ -58,12 +58,12 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Embedding: + ) -> EmbeddingCreateResponse: """ Embed legal texts with an Isaacus legal AI embedder. Args: - model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + model: The ID of the [model](https://docs.isaacus.com/models#embeddings) to use for embedding. texts: The text or array of texts to embed. @@ -113,7 +113,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Embedding, + cast_to=EmbeddingCreateResponse, ) @@ -151,12 +151,12 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Embedding: + ) -> EmbeddingCreateResponse: """ Embed legal texts with an Isaacus legal AI embedder. Args: - model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + model: The ID of the [model](https://docs.isaacus.com/models#embeddings) to use for embedding. texts: The text or array of texts to embed. @@ -206,7 +206,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Embedding, + cast_to=EmbeddingCreateResponse, ) diff --git a/src/isaacus/types/__init__.py b/src/isaacus/types/__init__.py index c3b6053..2d147ba 100644 --- a/src/isaacus/types/__init__.py +++ b/src/isaacus/types/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .embedding import Embedding as Embedding from .reranking import Reranking as Reranking from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .reranking_create_params import RerankingCreateParams as RerankingCreateParams +from .embedding_create_response import EmbeddingCreateResponse as EmbeddingCreateResponse diff --git a/src/isaacus/types/embedding_create_params.py b/src/isaacus/types/embedding_create_params.py index 3600291..513fe6f 100644 --- a/src/isaacus/types/embedding_create_params.py +++ b/src/isaacus/types/embedding_create_params.py @@ -13,7 +13,7 @@ class EmbeddingCreateParams(TypedDict, total=False): model: Required[Literal["kanon-2-embedder"]] """ - The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + The ID of the [model](https://docs.isaacus.com/models#embeddings) to use for embedding. """ diff --git a/src/isaacus/types/embedding.py b/src/isaacus/types/embedding_create_response.py similarity index 53% rename from src/isaacus/types/embedding.py rename to src/isaacus/types/embedding_create_response.py index ddc8bec..15ce12c 100644 --- a/src/isaacus/types/embedding.py +++ b/src/isaacus/types/embedding_create_response.py @@ -4,7 +4,18 @@ from .._models import BaseModel -__all__ = ["Embedding", "Usage"] +__all__ = ["EmbeddingCreateResponse", "Embedding", "Usage"] + + +class Embedding(BaseModel): + embedding: List[float] + """The embedding of the content represented as an array of floating point numbers.""" + + index: int + """ + The position of the content in the input array of contents, starting from `0` + (and, therefore, ending at the number of contents minus `1`). + """ class Usage(BaseModel): @@ -12,7 +23,7 @@ class Usage(BaseModel): """The number of tokens inputted to the model.""" -class Embedding(BaseModel): +class EmbeddingCreateResponse(BaseModel): embeddings: List[Embedding] """The embeddings of the inputs.""" diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py index 45cfc6a..1cebcb2 100644 --- a/tests/api_resources/test_embeddings.py +++ b/tests/api_resources/test_embeddings.py @@ -9,7 +9,7 @@ from isaacus import Isaacus, AsyncIsaacus from tests.utils import assert_matches_type -from isaacus.types import Embedding +from isaacus.types import EmbeddingCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -24,7 +24,7 @@ def test_method_create(self, client: Isaacus) -> None: model="kanon-2-embedder", texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -36,7 +36,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: overflow_strategy="drop_end", task="retrieval/query", ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -49,7 +49,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -62,7 +62,7 @@ def test_streaming_response_create(self, client: Isaacus) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) assert cast(Any, response.is_closed) is True @@ -79,7 +79,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: model="kanon-2-embedder", texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -91,7 +91,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - overflow_strategy="drop_end", task="retrieval/query", ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -104,7 +104,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = await response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -117,6 +117,6 @@ async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = await response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) assert cast(Any, response.is_closed) is True From 3074c3a84109cb3069caebc7b263c8c92644054c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 07:20:58 +0000 Subject: [PATCH 26/38] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1cf1012..a846487 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-bc90df05434e3f9e249881744c600bce6e9d04f43d05c1fafa7133d02114f393.yml -openapi_spec_hash: 2205376b167519bd09d2f95741e6141c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-fcd541cb8fbd03d20e7f02e296ee5fe35e7e6535a6926bd46cd1c4e8423b91ff.yml +openapi_spec_hash: 8c588c5cc113ffe42c5aa9ada04169ab config_hash: 886b5eef0dbd90b8e6686e987a07b816 From b9342795e50374817b8e3dc2e2f1163a2ff0805a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 07:21:30 +0000 Subject: [PATCH 27/38] feat(api): revert embedding -> embeddings --- .stats.yml | 4 ++-- api.md | 4 ++-- src/isaacus/resources/embeddings.py | 14 +++++++------- src/isaacus/types/__init__.py | 2 +- ...bedding_create_response.py => embedding.py} | 15 ++------------- src/isaacus/types/embedding_create_params.py | 2 +- tests/api_resources/test_embeddings.py | 18 +++++++++--------- 7 files changed, 24 insertions(+), 35 deletions(-) rename src/isaacus/types/{embedding_create_response.py => embedding.py} (53%) diff --git a/.stats.yml b/.stats.yml index a846487..8b6ec3e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-fcd541cb8fbd03d20e7f02e296ee5fe35e7e6535a6926bd46cd1c4e8423b91ff.yml -openapi_spec_hash: 8c588c5cc113ffe42c5aa9ada04169ab +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-e92a3b3731be1b1223f26fce1a50165c75ea209097e89a530a6123a59c672898.yml +openapi_spec_hash: ba79dfb28159963468d872e717579e94 config_hash: 886b5eef0dbd90b8e6686e987a07b816 diff --git a/api.md b/api.md index e61008c..6a1d5c7 100644 --- a/api.md +++ b/api.md @@ -3,12 +3,12 @@ Types: ```python -from isaacus.types import EmbeddingCreateResponse +from isaacus.types import Embedding ``` Methods: -- client.embeddings.create(\*\*params) -> EmbeddingCreateResponse +- client.embeddings.create(\*\*params) -> Embedding # Classifications diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py index da0b58a..5179230 100644 --- a/src/isaacus/resources/embeddings.py +++ b/src/isaacus/resources/embeddings.py @@ -19,7 +19,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.embedding_create_response import EmbeddingCreateResponse +from ..types.embedding import Embedding __all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] @@ -58,12 +58,12 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmbeddingCreateResponse: + ) -> Embedding: """ Embed legal texts with an Isaacus legal AI embedder. Args: - model: The ID of the [model](https://docs.isaacus.com/models#embeddings) to use for + model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for embedding. texts: The text or array of texts to embed. @@ -113,7 +113,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=EmbeddingCreateResponse, + cast_to=Embedding, ) @@ -151,12 +151,12 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EmbeddingCreateResponse: + ) -> Embedding: """ Embed legal texts with an Isaacus legal AI embedder. Args: - model: The ID of the [model](https://docs.isaacus.com/models#embeddings) to use for + model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for embedding. texts: The text or array of texts to embed. @@ -206,7 +206,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=EmbeddingCreateResponse, + cast_to=Embedding, ) diff --git a/src/isaacus/types/__init__.py b/src/isaacus/types/__init__.py index 2d147ba..c3b6053 100644 --- a/src/isaacus/types/__init__.py +++ b/src/isaacus/types/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations +from .embedding import Embedding as Embedding from .reranking import Reranking as Reranking from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .reranking_create_params import RerankingCreateParams as RerankingCreateParams -from .embedding_create_response import EmbeddingCreateResponse as EmbeddingCreateResponse diff --git a/src/isaacus/types/embedding_create_response.py b/src/isaacus/types/embedding.py similarity index 53% rename from src/isaacus/types/embedding_create_response.py rename to src/isaacus/types/embedding.py index 15ce12c..ddc8bec 100644 --- a/src/isaacus/types/embedding_create_response.py +++ b/src/isaacus/types/embedding.py @@ -4,18 +4,7 @@ from .._models import BaseModel -__all__ = ["EmbeddingCreateResponse", "Embedding", "Usage"] - - -class Embedding(BaseModel): - embedding: List[float] - """The embedding of the content represented as an array of floating point numbers.""" - - index: int - """ - The position of the content in the input array of contents, starting from `0` - (and, therefore, ending at the number of contents minus `1`). - """ +__all__ = ["Embedding", "Usage"] class Usage(BaseModel): @@ -23,7 +12,7 @@ class Usage(BaseModel): """The number of tokens inputted to the model.""" -class EmbeddingCreateResponse(BaseModel): +class Embedding(BaseModel): embeddings: List[Embedding] """The embeddings of the inputs.""" diff --git a/src/isaacus/types/embedding_create_params.py b/src/isaacus/types/embedding_create_params.py index 513fe6f..3600291 100644 --- a/src/isaacus/types/embedding_create_params.py +++ b/src/isaacus/types/embedding_create_params.py @@ -13,7 +13,7 @@ class EmbeddingCreateParams(TypedDict, total=False): model: Required[Literal["kanon-2-embedder"]] """ - The ID of the [model](https://docs.isaacus.com/models#embeddings) to use for + The ID of the [model](https://docs.isaacus.com/models#embedding) to use for embedding. """ diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py index 1cebcb2..45cfc6a 100644 --- a/tests/api_resources/test_embeddings.py +++ b/tests/api_resources/test_embeddings.py @@ -9,7 +9,7 @@ from isaacus import Isaacus, AsyncIsaacus from tests.utils import assert_matches_type -from isaacus.types import EmbeddingCreateResponse +from isaacus.types import Embedding base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -24,7 +24,7 @@ def test_method_create(self, client: Isaacus) -> None: model="kanon-2-embedder", texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -36,7 +36,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: overflow_strategy="drop_end", task="retrieval/query", ) - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -49,7 +49,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -62,7 +62,7 @@ def test_streaming_response_create(self, client: Isaacus) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) assert cast(Any, response.is_closed) is True @@ -79,7 +79,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: model="kanon-2-embedder", texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -91,7 +91,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - overflow_strategy="drop_end", task="retrieval/query", ) - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -104,7 +104,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = await response.parse() - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -117,6 +117,6 @@ async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = await response.parse() - assert_matches_type(EmbeddingCreateResponse, embedding, path=["response"]) + assert_matches_type(Embedding, embedding, path=["response"]) assert cast(Any, response.is_closed) is True From 4c1cfaa8dce7f3b85bb9edb6d82fa82574163c13 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 07:31:55 +0000 Subject: [PATCH 28/38] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8b6ec3e..c4c2f90 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-e92a3b3731be1b1223f26fce1a50165c75ea209097e89a530a6123a59c672898.yml -openapi_spec_hash: ba79dfb28159963468d872e717579e94 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-30c23c1facb9383eb94d78d16d6a45b7e6afd943caf94cddeb75fd86357294f7.yml +openapi_spec_hash: 48a6b3192add4f538615ac6f190123c5 config_hash: 886b5eef0dbd90b8e6686e987a07b816 From 2fafb555c1a20d7c359c91c35fd1f54868cffe54 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 07:37:52 +0000 Subject: [PATCH 29/38] chore(api): try to force regen SDK --- .stats.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c4c2f90..e2fe56f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-30c23c1facb9383eb94d78d16d6a45b7e6afd943caf94cddeb75fd86357294f7.yml openapi_spec_hash: 48a6b3192add4f538615ac6f190123c5 -config_hash: 886b5eef0dbd90b8e6686e987a07b816 +config_hash: d6b142057661ed5b068cbe07895e7d50 diff --git a/README.md b/README.md index 6d62191..e9e962f 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ universal_classification = client.classifications.universal.create( query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], chunking_options={ - "overlap_ratio": 0.1, + "overlap_ratio": 0.2, "overlap_tokens": None, "size": 512, }, From 079645e85259c2e4d3f6aa86b2ca2c21ce97367a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:13:38 +0000 Subject: [PATCH 30/38] chore(sdk): restore original example --- .stats.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e2fe56f..c4c2f90 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-30c23c1facb9383eb94d78d16d6a45b7e6afd943caf94cddeb75fd86357294f7.yml openapi_spec_hash: 48a6b3192add4f538615ac6f190123c5 -config_hash: d6b142057661ed5b068cbe07895e7d50 +config_hash: 886b5eef0dbd90b8e6686e987a07b816 diff --git a/README.md b/README.md index e9e962f..6d62191 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ universal_classification = client.classifications.universal.create( query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], chunking_options={ - "overlap_ratio": 0.2, + "overlap_ratio": 0.1, "overlap_tokens": None, "size": 512, }, From cf60482ba0dd3933daee477fa9bd4ae29d900fb4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:14:10 +0000 Subject: [PATCH 31/38] feat(sdk): toggle to force regen --- .stats.yml | 4 +- api.md | 12 - src/isaacus/_client.py | 10 +- src/isaacus/resources/__init__.py | 14 -- src/isaacus/resources/embeddings.py | 246 ------------------- src/isaacus/types/__init__.py | 2 - src/isaacus/types/embedding.py | 20 -- src/isaacus/types/embedding_create_params.py | 49 ---- tests/api_resources/test_embeddings.py | 122 --------- 9 files changed, 3 insertions(+), 476 deletions(-) delete mode 100644 src/isaacus/resources/embeddings.py delete mode 100644 src/isaacus/types/embedding.py delete mode 100644 src/isaacus/types/embedding_create_params.py delete mode 100644 tests/api_resources/test_embeddings.py diff --git a/.stats.yml b/.stats.yml index c4c2f90..4953d29 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 4 +configured_endpoints: 3 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-30c23c1facb9383eb94d78d16d6a45b7e6afd943caf94cddeb75fd86357294f7.yml openapi_spec_hash: 48a6b3192add4f538615ac6f190123c5 -config_hash: 886b5eef0dbd90b8e6686e987a07b816 +config_hash: 83740a990f84ee8d189fc4071a009a83 diff --git a/api.md b/api.md index 6a1d5c7..a3a6143 100644 --- a/api.md +++ b/api.md @@ -1,15 +1,3 @@ -# Embeddings - -Types: - -```python -from isaacus.types import Embedding -``` - -Methods: - -- client.embeddings.create(\*\*params) -> Embedding - # Classifications ## Universal diff --git a/src/isaacus/_client.py b/src/isaacus/_client.py index 990b9e6..7bbac58 100644 --- a/src/isaacus/_client.py +++ b/src/isaacus/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import embeddings, rerankings +from .resources import rerankings from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import IsaacusError, APIStatusError from ._base_client import ( @@ -36,7 +36,6 @@ class Isaacus(SyncAPIClient): - embeddings: embeddings.EmbeddingsResource classifications: classifications.ClassificationsResource rerankings: rerankings.RerankingsResource extractions: extractions.ExtractionsResource @@ -97,7 +96,6 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.embeddings = embeddings.EmbeddingsResource(self) self.classifications = classifications.ClassificationsResource(self) self.rerankings = rerankings.RerankingsResource(self) self.extractions = extractions.ExtractionsResource(self) @@ -210,7 +208,6 @@ def _make_status_error( class AsyncIsaacus(AsyncAPIClient): - embeddings: embeddings.AsyncEmbeddingsResource classifications: classifications.AsyncClassificationsResource rerankings: rerankings.AsyncRerankingsResource extractions: extractions.AsyncExtractionsResource @@ -271,7 +268,6 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.embeddings = embeddings.AsyncEmbeddingsResource(self) self.classifications = classifications.AsyncClassificationsResource(self) self.rerankings = rerankings.AsyncRerankingsResource(self) self.extractions = extractions.AsyncExtractionsResource(self) @@ -385,7 +381,6 @@ def _make_status_error( class IsaacusWithRawResponse: def __init__(self, client: Isaacus) -> None: - self.embeddings = embeddings.EmbeddingsResourceWithRawResponse(client.embeddings) self.classifications = classifications.ClassificationsResourceWithRawResponse(client.classifications) self.rerankings = rerankings.RerankingsResourceWithRawResponse(client.rerankings) self.extractions = extractions.ExtractionsResourceWithRawResponse(client.extractions) @@ -393,7 +388,6 @@ def __init__(self, client: Isaacus) -> None: class AsyncIsaacusWithRawResponse: def __init__(self, client: AsyncIsaacus) -> None: - self.embeddings = embeddings.AsyncEmbeddingsResourceWithRawResponse(client.embeddings) self.classifications = classifications.AsyncClassificationsResourceWithRawResponse(client.classifications) self.rerankings = rerankings.AsyncRerankingsResourceWithRawResponse(client.rerankings) self.extractions = extractions.AsyncExtractionsResourceWithRawResponse(client.extractions) @@ -401,7 +395,6 @@ def __init__(self, client: AsyncIsaacus) -> None: class IsaacusWithStreamedResponse: def __init__(self, client: Isaacus) -> None: - self.embeddings = embeddings.EmbeddingsResourceWithStreamingResponse(client.embeddings) self.classifications = classifications.ClassificationsResourceWithStreamingResponse(client.classifications) self.rerankings = rerankings.RerankingsResourceWithStreamingResponse(client.rerankings) self.extractions = extractions.ExtractionsResourceWithStreamingResponse(client.extractions) @@ -409,7 +402,6 @@ def __init__(self, client: Isaacus) -> None: class AsyncIsaacusWithStreamedResponse: def __init__(self, client: AsyncIsaacus) -> None: - self.embeddings = embeddings.AsyncEmbeddingsResourceWithStreamingResponse(client.embeddings) self.classifications = classifications.AsyncClassificationsResourceWithStreamingResponse(client.classifications) self.rerankings = rerankings.AsyncRerankingsResourceWithStreamingResponse(client.rerankings) self.extractions = extractions.AsyncExtractionsResourceWithStreamingResponse(client.extractions) diff --git a/src/isaacus/resources/__init__.py b/src/isaacus/resources/__init__.py index 3c62ff4..e55176e 100644 --- a/src/isaacus/resources/__init__.py +++ b/src/isaacus/resources/__init__.py @@ -1,13 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .embeddings import ( - EmbeddingsResource, - AsyncEmbeddingsResource, - EmbeddingsResourceWithRawResponse, - AsyncEmbeddingsResourceWithRawResponse, - EmbeddingsResourceWithStreamingResponse, - AsyncEmbeddingsResourceWithStreamingResponse, -) from .rerankings import ( RerankingsResource, AsyncRerankingsResource, @@ -34,12 +26,6 @@ ) __all__ = [ - "EmbeddingsResource", - "AsyncEmbeddingsResource", - "EmbeddingsResourceWithRawResponse", - "AsyncEmbeddingsResourceWithRawResponse", - "EmbeddingsResourceWithStreamingResponse", - "AsyncEmbeddingsResourceWithStreamingResponse", "ClassificationsResource", "AsyncClassificationsResource", "ClassificationsResourceWithRawResponse", diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py deleted file mode 100644 index 5179230..0000000 --- a/src/isaacus/resources/embeddings.py +++ /dev/null @@ -1,246 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union, Optional -from typing_extensions import Literal - -import httpx - -from ..types import embedding_create_params -from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.embedding import Embedding - -__all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] - - -class EmbeddingsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> EmbeddingsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/isaacus-dev/isaacus-python#accessing-raw-response-data-eg-headers - """ - return EmbeddingsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> EmbeddingsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/isaacus-dev/isaacus-python#with_streaming_response - """ - return EmbeddingsResourceWithStreamingResponse(self) - - def create( - self, - *, - model: Literal["kanon-2-embedder"], - texts: Union[SequenceNotStr[str], str], - dimensions: Optional[int] | Omit = omit, - overflow_strategy: Optional[Literal["drop_end"]] | Omit = omit, - task: Optional[Literal["retrieval/query", "retrieval/document"]] | 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. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Embedding: - """ - Embed legal texts with an Isaacus legal AI embedder. - - Args: - model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for - embedding. - - texts: The text or array of texts to embed. - - Each text must contain at least one non-whitespace character. - - No more than 1,000 texts can be embedded in a single request. - - dimensions: A whole number greater than or equal to 1. - - overflow_strategy: The strategy to employ when content exceeds the model's maximum input length. - - `drop_end`, which is the default setting, drops tokens from the end of the - content exceeding the limit. - - If `null`, an error will be raised if any content exceeds the model's maximum - input length. - - task: The task the embeddings will be used for. - - `retrieval/query` is meant for queries and statements, and `retrieval/document` - is meant for anything to be retrieved using query embeddings. - - If `null`, which is the default setting, embeddings will not be optimized for - any particular task. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/embeddings", - body=maybe_transform( - { - "model": model, - "texts": texts, - "dimensions": dimensions, - "overflow_strategy": overflow_strategy, - "task": task, - }, - embedding_create_params.EmbeddingCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Embedding, - ) - - -class AsyncEmbeddingsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncEmbeddingsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/isaacus-dev/isaacus-python#accessing-raw-response-data-eg-headers - """ - return AsyncEmbeddingsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncEmbeddingsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/isaacus-dev/isaacus-python#with_streaming_response - """ - return AsyncEmbeddingsResourceWithStreamingResponse(self) - - async def create( - self, - *, - model: Literal["kanon-2-embedder"], - texts: Union[SequenceNotStr[str], str], - dimensions: Optional[int] | Omit = omit, - overflow_strategy: Optional[Literal["drop_end"]] | Omit = omit, - task: Optional[Literal["retrieval/query", "retrieval/document"]] | 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. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Embedding: - """ - Embed legal texts with an Isaacus legal AI embedder. - - Args: - model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for - embedding. - - texts: The text or array of texts to embed. - - Each text must contain at least one non-whitespace character. - - No more than 1,000 texts can be embedded in a single request. - - dimensions: A whole number greater than or equal to 1. - - overflow_strategy: The strategy to employ when content exceeds the model's maximum input length. - - `drop_end`, which is the default setting, drops tokens from the end of the - content exceeding the limit. - - If `null`, an error will be raised if any content exceeds the model's maximum - input length. - - task: The task the embeddings will be used for. - - `retrieval/query` is meant for queries and statements, and `retrieval/document` - is meant for anything to be retrieved using query embeddings. - - If `null`, which is the default setting, embeddings will not be optimized for - any particular task. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/embeddings", - body=await async_maybe_transform( - { - "model": model, - "texts": texts, - "dimensions": dimensions, - "overflow_strategy": overflow_strategy, - "task": task, - }, - embedding_create_params.EmbeddingCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Embedding, - ) - - -class EmbeddingsResourceWithRawResponse: - def __init__(self, embeddings: EmbeddingsResource) -> None: - self._embeddings = embeddings - - self.create = to_raw_response_wrapper( - embeddings.create, - ) - - -class AsyncEmbeddingsResourceWithRawResponse: - def __init__(self, embeddings: AsyncEmbeddingsResource) -> None: - self._embeddings = embeddings - - self.create = async_to_raw_response_wrapper( - embeddings.create, - ) - - -class EmbeddingsResourceWithStreamingResponse: - def __init__(self, embeddings: EmbeddingsResource) -> None: - self._embeddings = embeddings - - self.create = to_streamed_response_wrapper( - embeddings.create, - ) - - -class AsyncEmbeddingsResourceWithStreamingResponse: - def __init__(self, embeddings: AsyncEmbeddingsResource) -> None: - self._embeddings = embeddings - - self.create = async_to_streamed_response_wrapper( - embeddings.create, - ) diff --git a/src/isaacus/types/__init__.py b/src/isaacus/types/__init__.py index c3b6053..e5c4540 100644 --- a/src/isaacus/types/__init__.py +++ b/src/isaacus/types/__init__.py @@ -2,7 +2,5 @@ from __future__ import annotations -from .embedding import Embedding as Embedding from .reranking import Reranking as Reranking -from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .reranking_create_params import RerankingCreateParams as RerankingCreateParams diff --git a/src/isaacus/types/embedding.py b/src/isaacus/types/embedding.py deleted file mode 100644 index ddc8bec..0000000 --- a/src/isaacus/types/embedding.py +++ /dev/null @@ -1,20 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List - -from .._models import BaseModel - -__all__ = ["Embedding", "Usage"] - - -class Usage(BaseModel): - input_tokens: int - """The number of tokens inputted to the model.""" - - -class Embedding(BaseModel): - embeddings: List[Embedding] - """The embeddings of the inputs.""" - - usage: Usage - """Statistics about the usage of resources in the process of embedding the inputs.""" diff --git a/src/isaacus/types/embedding_create_params.py b/src/isaacus/types/embedding_create_params.py deleted file mode 100644 index 3600291..0000000 --- a/src/isaacus/types/embedding_create_params.py +++ /dev/null @@ -1,49 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union, Optional -from typing_extensions import Literal, Required, TypedDict - -from .._types import SequenceNotStr - -__all__ = ["EmbeddingCreateParams"] - - -class EmbeddingCreateParams(TypedDict, total=False): - model: Required[Literal["kanon-2-embedder"]] - """ - The ID of the [model](https://docs.isaacus.com/models#embedding) to use for - embedding. - """ - - texts: Required[Union[SequenceNotStr[str], str]] - """The text or array of texts to embed. - - Each text must contain at least one non-whitespace character. - - No more than 1,000 texts can be embedded in a single request. - """ - - dimensions: Optional[int] - """A whole number greater than or equal to 1.""" - - overflow_strategy: Optional[Literal["drop_end"]] - """The strategy to employ when content exceeds the model's maximum input length. - - `drop_end`, which is the default setting, drops tokens from the end of the - content exceeding the limit. - - If `null`, an error will be raised if any content exceeds the model's maximum - input length. - """ - - task: Optional[Literal["retrieval/query", "retrieval/document"]] - """The task the embeddings will be used for. - - `retrieval/query` is meant for queries and statements, and `retrieval/document` - is meant for anything to be retrieved using query embeddings. - - If `null`, which is the default setting, embeddings will not be optimized for - any particular task. - """ diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py deleted file mode 100644 index 45cfc6a..0000000 --- a/tests/api_resources/test_embeddings.py +++ /dev/null @@ -1,122 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from isaacus import Isaacus, AsyncIsaacus -from tests.utils import assert_matches_type -from isaacus.types import Embedding - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestEmbeddings: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_create(self, client: Isaacus) -> None: - embedding = client.embeddings.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - ) - assert_matches_type(Embedding, embedding, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_create_with_all_params(self, client: Isaacus) -> None: - embedding = client.embeddings.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - dimensions=1, - overflow_strategy="drop_end", - task="retrieval/query", - ) - assert_matches_type(Embedding, embedding, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_create(self, client: Isaacus) -> None: - response = client.embeddings.with_raw_response.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - embedding = response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_create(self, client: Isaacus) -> None: - with client.embeddings.with_streaming_response.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - embedding = response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncEmbeddings: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_create(self, async_client: AsyncIsaacus) -> None: - embedding = await async_client.embeddings.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - ) - assert_matches_type(Embedding, embedding, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: - embedding = await async_client.embeddings.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - dimensions=1, - overflow_strategy="drop_end", - task="retrieval/query", - ) - assert_matches_type(Embedding, embedding, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: - response = await async_client.embeddings.with_raw_response.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - embedding = await response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: - async with async_client.embeddings.with_streaming_response.create( - model="kanon-2-embedder", - texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - embedding = await response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) - - assert cast(Any, response.is_closed) is True From 25d2067fad4bb46ca595001f6e82458fd3d24a23 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:15:20 +0000 Subject: [PATCH 32/38] feat(sdk): untoggle to force regen --- .stats.yml | 4 +- api.md | 12 + src/isaacus/_client.py | 10 +- src/isaacus/resources/__init__.py | 14 ++ src/isaacus/resources/embeddings.py | 246 +++++++++++++++++++ src/isaacus/types/__init__.py | 2 + src/isaacus/types/embedding.py | 20 ++ src/isaacus/types/embedding_create_params.py | 49 ++++ tests/api_resources/test_embeddings.py | 122 +++++++++ 9 files changed, 476 insertions(+), 3 deletions(-) create mode 100644 src/isaacus/resources/embeddings.py create mode 100644 src/isaacus/types/embedding.py create mode 100644 src/isaacus/types/embedding_create_params.py create mode 100644 tests/api_resources/test_embeddings.py diff --git a/.stats.yml b/.stats.yml index 4953d29..c4c2f90 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 3 +configured_endpoints: 4 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-30c23c1facb9383eb94d78d16d6a45b7e6afd943caf94cddeb75fd86357294f7.yml openapi_spec_hash: 48a6b3192add4f538615ac6f190123c5 -config_hash: 83740a990f84ee8d189fc4071a009a83 +config_hash: 886b5eef0dbd90b8e6686e987a07b816 diff --git a/api.md b/api.md index a3a6143..6a1d5c7 100644 --- a/api.md +++ b/api.md @@ -1,3 +1,15 @@ +# Embeddings + +Types: + +```python +from isaacus.types import Embedding +``` + +Methods: + +- client.embeddings.create(\*\*params) -> Embedding + # Classifications ## Universal diff --git a/src/isaacus/_client.py b/src/isaacus/_client.py index 7bbac58..990b9e6 100644 --- a/src/isaacus/_client.py +++ b/src/isaacus/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import rerankings +from .resources import embeddings, rerankings from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import IsaacusError, APIStatusError from ._base_client import ( @@ -36,6 +36,7 @@ class Isaacus(SyncAPIClient): + embeddings: embeddings.EmbeddingsResource classifications: classifications.ClassificationsResource rerankings: rerankings.RerankingsResource extractions: extractions.ExtractionsResource @@ -96,6 +97,7 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self.embeddings = embeddings.EmbeddingsResource(self) self.classifications = classifications.ClassificationsResource(self) self.rerankings = rerankings.RerankingsResource(self) self.extractions = extractions.ExtractionsResource(self) @@ -208,6 +210,7 @@ def _make_status_error( class AsyncIsaacus(AsyncAPIClient): + embeddings: embeddings.AsyncEmbeddingsResource classifications: classifications.AsyncClassificationsResource rerankings: rerankings.AsyncRerankingsResource extractions: extractions.AsyncExtractionsResource @@ -268,6 +271,7 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self.embeddings = embeddings.AsyncEmbeddingsResource(self) self.classifications = classifications.AsyncClassificationsResource(self) self.rerankings = rerankings.AsyncRerankingsResource(self) self.extractions = extractions.AsyncExtractionsResource(self) @@ -381,6 +385,7 @@ def _make_status_error( class IsaacusWithRawResponse: def __init__(self, client: Isaacus) -> None: + self.embeddings = embeddings.EmbeddingsResourceWithRawResponse(client.embeddings) self.classifications = classifications.ClassificationsResourceWithRawResponse(client.classifications) self.rerankings = rerankings.RerankingsResourceWithRawResponse(client.rerankings) self.extractions = extractions.ExtractionsResourceWithRawResponse(client.extractions) @@ -388,6 +393,7 @@ def __init__(self, client: Isaacus) -> None: class AsyncIsaacusWithRawResponse: def __init__(self, client: AsyncIsaacus) -> None: + self.embeddings = embeddings.AsyncEmbeddingsResourceWithRawResponse(client.embeddings) self.classifications = classifications.AsyncClassificationsResourceWithRawResponse(client.classifications) self.rerankings = rerankings.AsyncRerankingsResourceWithRawResponse(client.rerankings) self.extractions = extractions.AsyncExtractionsResourceWithRawResponse(client.extractions) @@ -395,6 +401,7 @@ def __init__(self, client: AsyncIsaacus) -> None: class IsaacusWithStreamedResponse: def __init__(self, client: Isaacus) -> None: + self.embeddings = embeddings.EmbeddingsResourceWithStreamingResponse(client.embeddings) self.classifications = classifications.ClassificationsResourceWithStreamingResponse(client.classifications) self.rerankings = rerankings.RerankingsResourceWithStreamingResponse(client.rerankings) self.extractions = extractions.ExtractionsResourceWithStreamingResponse(client.extractions) @@ -402,6 +409,7 @@ def __init__(self, client: Isaacus) -> None: class AsyncIsaacusWithStreamedResponse: def __init__(self, client: AsyncIsaacus) -> None: + self.embeddings = embeddings.AsyncEmbeddingsResourceWithStreamingResponse(client.embeddings) self.classifications = classifications.AsyncClassificationsResourceWithStreamingResponse(client.classifications) self.rerankings = rerankings.AsyncRerankingsResourceWithStreamingResponse(client.rerankings) self.extractions = extractions.AsyncExtractionsResourceWithStreamingResponse(client.extractions) diff --git a/src/isaacus/resources/__init__.py b/src/isaacus/resources/__init__.py index e55176e..3c62ff4 100644 --- a/src/isaacus/resources/__init__.py +++ b/src/isaacus/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .embeddings import ( + EmbeddingsResource, + AsyncEmbeddingsResource, + EmbeddingsResourceWithRawResponse, + AsyncEmbeddingsResourceWithRawResponse, + EmbeddingsResourceWithStreamingResponse, + AsyncEmbeddingsResourceWithStreamingResponse, +) from .rerankings import ( RerankingsResource, AsyncRerankingsResource, @@ -26,6 +34,12 @@ ) __all__ = [ + "EmbeddingsResource", + "AsyncEmbeddingsResource", + "EmbeddingsResourceWithRawResponse", + "AsyncEmbeddingsResourceWithRawResponse", + "EmbeddingsResourceWithStreamingResponse", + "AsyncEmbeddingsResourceWithStreamingResponse", "ClassificationsResource", "AsyncClassificationsResource", "ClassificationsResourceWithRawResponse", diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py new file mode 100644 index 0000000..5179230 --- /dev/null +++ b/src/isaacus/resources/embeddings.py @@ -0,0 +1,246 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal + +import httpx + +from ..types import embedding_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.embedding import Embedding + +__all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] + + +class EmbeddingsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> EmbeddingsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#accessing-raw-response-data-eg-headers + """ + return EmbeddingsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> EmbeddingsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#with_streaming_response + """ + return EmbeddingsResourceWithStreamingResponse(self) + + def create( + self, + *, + model: Literal["kanon-2-embedder"], + texts: Union[SequenceNotStr[str], str], + dimensions: Optional[int] | Omit = omit, + overflow_strategy: Optional[Literal["drop_end"]] | Omit = omit, + task: Optional[Literal["retrieval/query", "retrieval/document"]] | 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Embedding: + """ + Embed legal texts with an Isaacus legal AI embedder. + + Args: + model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + embedding. + + texts: The text or array of texts to embed. + + Each text must contain at least one non-whitespace character. + + No more than 1,000 texts can be embedded in a single request. + + dimensions: A whole number greater than or equal to 1. + + overflow_strategy: The strategy to employ when content exceeds the model's maximum input length. + + `drop_end`, which is the default setting, drops tokens from the end of the + content exceeding the limit. + + If `null`, an error will be raised if any content exceeds the model's maximum + input length. + + task: The task the embeddings will be used for. + + `retrieval/query` is meant for queries and statements, and `retrieval/document` + is meant for anything to be retrieved using query embeddings. + + If `null`, which is the default setting, embeddings will not be optimized for + any particular task. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/embeddings", + body=maybe_transform( + { + "model": model, + "texts": texts, + "dimensions": dimensions, + "overflow_strategy": overflow_strategy, + "task": task, + }, + embedding_create_params.EmbeddingCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Embedding, + ) + + +class AsyncEmbeddingsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncEmbeddingsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#accessing-raw-response-data-eg-headers + """ + return AsyncEmbeddingsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncEmbeddingsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/isaacus-dev/isaacus-python#with_streaming_response + """ + return AsyncEmbeddingsResourceWithStreamingResponse(self) + + async def create( + self, + *, + model: Literal["kanon-2-embedder"], + texts: Union[SequenceNotStr[str], str], + dimensions: Optional[int] | Omit = omit, + overflow_strategy: Optional[Literal["drop_end"]] | Omit = omit, + task: Optional[Literal["retrieval/query", "retrieval/document"]] | 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Embedding: + """ + Embed legal texts with an Isaacus legal AI embedder. + + Args: + model: The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + embedding. + + texts: The text or array of texts to embed. + + Each text must contain at least one non-whitespace character. + + No more than 1,000 texts can be embedded in a single request. + + dimensions: A whole number greater than or equal to 1. + + overflow_strategy: The strategy to employ when content exceeds the model's maximum input length. + + `drop_end`, which is the default setting, drops tokens from the end of the + content exceeding the limit. + + If `null`, an error will be raised if any content exceeds the model's maximum + input length. + + task: The task the embeddings will be used for. + + `retrieval/query` is meant for queries and statements, and `retrieval/document` + is meant for anything to be retrieved using query embeddings. + + If `null`, which is the default setting, embeddings will not be optimized for + any particular task. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/embeddings", + body=await async_maybe_transform( + { + "model": model, + "texts": texts, + "dimensions": dimensions, + "overflow_strategy": overflow_strategy, + "task": task, + }, + embedding_create_params.EmbeddingCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Embedding, + ) + + +class EmbeddingsResourceWithRawResponse: + def __init__(self, embeddings: EmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = to_raw_response_wrapper( + embeddings.create, + ) + + +class AsyncEmbeddingsResourceWithRawResponse: + def __init__(self, embeddings: AsyncEmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = async_to_raw_response_wrapper( + embeddings.create, + ) + + +class EmbeddingsResourceWithStreamingResponse: + def __init__(self, embeddings: EmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = to_streamed_response_wrapper( + embeddings.create, + ) + + +class AsyncEmbeddingsResourceWithStreamingResponse: + def __init__(self, embeddings: AsyncEmbeddingsResource) -> None: + self._embeddings = embeddings + + self.create = async_to_streamed_response_wrapper( + embeddings.create, + ) diff --git a/src/isaacus/types/__init__.py b/src/isaacus/types/__init__.py index e5c4540..c3b6053 100644 --- a/src/isaacus/types/__init__.py +++ b/src/isaacus/types/__init__.py @@ -2,5 +2,7 @@ from __future__ import annotations +from .embedding import Embedding as Embedding from .reranking import Reranking as Reranking +from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .reranking_create_params import RerankingCreateParams as RerankingCreateParams diff --git a/src/isaacus/types/embedding.py b/src/isaacus/types/embedding.py new file mode 100644 index 0000000..ddc8bec --- /dev/null +++ b/src/isaacus/types/embedding.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel + +__all__ = ["Embedding", "Usage"] + + +class Usage(BaseModel): + input_tokens: int + """The number of tokens inputted to the model.""" + + +class Embedding(BaseModel): + embeddings: List[Embedding] + """The embeddings of the inputs.""" + + usage: Usage + """Statistics about the usage of resources in the process of embedding the inputs.""" diff --git a/src/isaacus/types/embedding_create_params.py b/src/isaacus/types/embedding_create_params.py new file mode 100644 index 0000000..3600291 --- /dev/null +++ b/src/isaacus/types/embedding_create_params.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["EmbeddingCreateParams"] + + +class EmbeddingCreateParams(TypedDict, total=False): + model: Required[Literal["kanon-2-embedder"]] + """ + The ID of the [model](https://docs.isaacus.com/models#embedding) to use for + embedding. + """ + + texts: Required[Union[SequenceNotStr[str], str]] + """The text or array of texts to embed. + + Each text must contain at least one non-whitespace character. + + No more than 1,000 texts can be embedded in a single request. + """ + + dimensions: Optional[int] + """A whole number greater than or equal to 1.""" + + overflow_strategy: Optional[Literal["drop_end"]] + """The strategy to employ when content exceeds the model's maximum input length. + + `drop_end`, which is the default setting, drops tokens from the end of the + content exceeding the limit. + + If `null`, an error will be raised if any content exceeds the model's maximum + input length. + """ + + task: Optional[Literal["retrieval/query", "retrieval/document"]] + """The task the embeddings will be used for. + + `retrieval/query` is meant for queries and statements, and `retrieval/document` + is meant for anything to be retrieved using query embeddings. + + If `null`, which is the default setting, embeddings will not be optimized for + any particular task. + """ diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py new file mode 100644 index 0000000..45cfc6a --- /dev/null +++ b/tests/api_resources/test_embeddings.py @@ -0,0 +1,122 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from isaacus import Isaacus, AsyncIsaacus +from tests.utils import assert_matches_type +from isaacus.types import Embedding + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestEmbeddings: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: Isaacus) -> None: + embedding = client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Isaacus) -> None: + embedding = client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + dimensions=1, + overflow_strategy="drop_end", + task="retrieval/query", + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Isaacus) -> None: + response = client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + embedding = response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Isaacus) -> None: + with client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + embedding = response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncEmbeddings: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncIsaacus) -> None: + embedding = await async_client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: + embedding = await async_client.embeddings.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + dimensions=1, + overflow_strategy="drop_end", + task="retrieval/query", + ) + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: + response = await async_client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + embedding = await response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: + async with async_client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + embedding = await response.parse() + assert_matches_type(Embedding, embedding, path=["response"]) + + assert cast(Any, response.is_closed) is True From b838d18513c7b4f5f9981baec287e8bb0801cbd5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:24:56 +0000 Subject: [PATCH 33/38] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c4c2f90..f69b282 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-30c23c1facb9383eb94d78d16d6a45b7e6afd943caf94cddeb75fd86357294f7.yml -openapi_spec_hash: 48a6b3192add4f538615ac6f190123c5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-43557cfcc912900321433e1b633b39a81a3bfdbcfc86784aff4ff8219f5ebec4.yml +openapi_spec_hash: e612b47282935484c5de2ebbd49df824 config_hash: 886b5eef0dbd90b8e6686e987a07b816 From 5c7462dd25c67c44126eb946a656a6b841dc6a50 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 08:25:34 +0000 Subject: [PATCH 34/38] fix(sdk)!: add `_response` to response models to finally fix duplicated names --- .stats.yml | 2 +- README.md | 16 ++++++++-------- api.md | 16 ++++++++-------- .../resources/classifications/universal.py | 10 +++++----- src/isaacus/resources/embeddings.py | 10 +++++----- src/isaacus/resources/extractions/qa.py | 10 +++++----- src/isaacus/resources/rerankings.py | 10 +++++----- src/isaacus/types/__init__.py | 4 ++-- src/isaacus/types/classifications/__init__.py | 2 +- ...py => universal_classification_response.py} | 4 ++-- .../{embedding.py => embedding_response.py} | 15 +++++++++++++-- src/isaacus/types/extractions/__init__.py | 2 +- ...action.py => answer_extraction_response.py} | 4 ++-- .../{reranking.py => reranking_response.py} | 4 ++-- .../classifications/test_universal.py | 18 +++++++++--------- tests/api_resources/extractions/test_qa.py | 18 +++++++++--------- tests/api_resources/test_embeddings.py | 18 +++++++++--------- tests/api_resources/test_rerankings.py | 18 +++++++++--------- 18 files changed, 96 insertions(+), 85 deletions(-) rename src/isaacus/types/classifications/{universal_classification.py => universal_classification_response.py} (94%) rename src/isaacus/types/{embedding.py => embedding_response.py} (54%) rename src/isaacus/types/extractions/{answer_extraction.py => answer_extraction_response.py} (94%) rename src/isaacus/types/{reranking.py => reranking_response.py} (90%) diff --git a/.stats.yml b/.stats.yml index f69b282..d798ff2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-43557cfcc912900321433e1b633b39a81a3bfdbcfc86784aff4ff8219f5ebec4.yml openapi_spec_hash: e612b47282935484c5de2ebbd49df824 -config_hash: 886b5eef0dbd90b8e6686e987a07b816 +config_hash: efa2ea406c5ecd6883ff8b0fb428e579 diff --git a/README.md b/README.md index 6d62191..6718e1c 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,12 @@ client = Isaacus( api_key=os.environ.get("ISAACUS_API_KEY"), # This is the default and can be omitted ) -universal_classification = client.classifications.universal.create( +universal_classification_response = client.classifications.universal.create( model="kanon-universal-classifier", query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], ) -print(universal_classification.classifications) +print(universal_classification_response.classifications) ``` While you can provide an `api_key` keyword argument, @@ -60,12 +60,12 @@ client = AsyncIsaacus( async def main() -> None: - universal_classification = await client.classifications.universal.create( + universal_classification_response = await client.classifications.universal.create( model="kanon-universal-classifier", query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], ) - print(universal_classification.classifications) + print(universal_classification_response.classifications) asyncio.run(main()) @@ -97,12 +97,12 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - universal_classification = await client.classifications.universal.create( + universal_classification_response = await client.classifications.universal.create( model="kanon-universal-classifier", query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], ) - print(universal_classification.classifications) + print(universal_classification_response.classifications) asyncio.run(main()) @@ -126,7 +126,7 @@ from isaacus import Isaacus client = Isaacus() -universal_classification = client.classifications.universal.create( +universal_classification_response = client.classifications.universal.create( model="kanon-universal-classifier", query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], @@ -136,7 +136,7 @@ universal_classification = client.classifications.universal.create( "size": 512, }, ) -print(universal_classification.classifications) +print(universal_classification_response.classifications) ``` ## Handling errors diff --git a/api.md b/api.md index 6a1d5c7..5ace2eb 100644 --- a/api.md +++ b/api.md @@ -3,12 +3,12 @@ Types: ```python -from isaacus.types import Embedding +from isaacus.types import EmbeddingResponse ``` Methods: -- client.embeddings.create(\*\*params) -> Embedding +- client.embeddings.create(\*\*params) -> EmbeddingResponse # Classifications @@ -17,24 +17,24 @@ Methods: Types: ```python -from isaacus.types.classifications import UniversalClassification +from isaacus.types.classifications import UniversalClassificationResponse ``` Methods: -- client.classifications.universal.create(\*\*params) -> UniversalClassification +- client.classifications.universal.create(\*\*params) -> UniversalClassificationResponse # Rerankings Types: ```python -from isaacus.types import Reranking +from isaacus.types import RerankingResponse ``` Methods: -- client.rerankings.create(\*\*params) -> Reranking +- client.rerankings.create(\*\*params) -> RerankingResponse # Extractions @@ -43,9 +43,9 @@ Methods: Types: ```python -from isaacus.types.extractions import AnswerExtraction +from isaacus.types.extractions import AnswerExtractionResponse ``` Methods: -- client.extractions.qa.create(\*\*params) -> AnswerExtraction +- client.extractions.qa.create(\*\*params) -> AnswerExtractionResponse diff --git a/src/isaacus/resources/classifications/universal.py b/src/isaacus/resources/classifications/universal.py index e700bbd..8aee937 100644 --- a/src/isaacus/resources/classifications/universal.py +++ b/src/isaacus/resources/classifications/universal.py @@ -19,7 +19,7 @@ ) from ..._base_client import make_request_options from ...types.classifications import universal_create_params -from ...types.classifications.universal_classification import UniversalClassification +from ...types.classifications.universal_classification_response import UniversalClassificationResponse __all__ = ["UniversalResource", "AsyncUniversalResource"] @@ -59,7 +59,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> UniversalClassification: + ) -> UniversalClassificationResponse: """ Classify the relevance of legal documents to a query with an Isaacus universal legal AI classifier. @@ -121,7 +121,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=UniversalClassification, + cast_to=UniversalClassificationResponse, ) @@ -160,7 +160,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> UniversalClassification: + ) -> UniversalClassificationResponse: """ Classify the relevance of legal documents to a query with an Isaacus universal legal AI classifier. @@ -222,7 +222,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=UniversalClassification, + cast_to=UniversalClassificationResponse, ) diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py index 5179230..3ab5f18 100644 --- a/src/isaacus/resources/embeddings.py +++ b/src/isaacus/resources/embeddings.py @@ -19,7 +19,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.embedding import Embedding +from ..types.embedding_response import EmbeddingResponse __all__ = ["EmbeddingsResource", "AsyncEmbeddingsResource"] @@ -58,7 +58,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Embedding: + ) -> EmbeddingResponse: """ Embed legal texts with an Isaacus legal AI embedder. @@ -113,7 +113,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Embedding, + cast_to=EmbeddingResponse, ) @@ -151,7 +151,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Embedding: + ) -> EmbeddingResponse: """ Embed legal texts with an Isaacus legal AI embedder. @@ -206,7 +206,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Embedding, + cast_to=EmbeddingResponse, ) diff --git a/src/isaacus/resources/extractions/qa.py b/src/isaacus/resources/extractions/qa.py index 303f0d1..b1ed8d7 100644 --- a/src/isaacus/resources/extractions/qa.py +++ b/src/isaacus/resources/extractions/qa.py @@ -19,7 +19,7 @@ ) from ..._base_client import make_request_options from ...types.extractions import qa_create_params -from ...types.extractions.answer_extraction import AnswerExtraction +from ...types.extractions.answer_extraction_response import AnswerExtractionResponse __all__ = ["QaResource", "AsyncQaResource"] @@ -59,7 +59,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AnswerExtraction: + ) -> AnswerExtractionResponse: """ Extract answers to questions from legal documents with an Isaacus legal AI answer extractor. @@ -119,7 +119,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AnswerExtraction, + cast_to=AnswerExtractionResponse, ) @@ -158,7 +158,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AnswerExtraction: + ) -> AnswerExtractionResponse: """ Extract answers to questions from legal documents with an Isaacus legal AI answer extractor. @@ -218,7 +218,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AnswerExtraction, + cast_to=AnswerExtractionResponse, ) diff --git a/src/isaacus/resources/rerankings.py b/src/isaacus/resources/rerankings.py index e839012..e389668 100644 --- a/src/isaacus/resources/rerankings.py +++ b/src/isaacus/resources/rerankings.py @@ -19,7 +19,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.reranking import Reranking +from ..types.reranking_response import RerankingResponse __all__ = ["RerankingsResource", "AsyncRerankingsResource"] @@ -60,7 +60,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Reranking: + ) -> RerankingResponse: """ Rerank legal documents by their relevance to a query with an Isaacus legal AI reranker. @@ -131,7 +131,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Reranking, + cast_to=RerankingResponse, ) @@ -171,7 +171,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Reranking: + ) -> RerankingResponse: """ Rerank legal documents by their relevance to a query with an Isaacus legal AI reranker. @@ -242,7 +242,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Reranking, + cast_to=RerankingResponse, ) diff --git a/src/isaacus/types/__init__.py b/src/isaacus/types/__init__.py index c3b6053..7a481fd 100644 --- a/src/isaacus/types/__init__.py +++ b/src/isaacus/types/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .embedding import Embedding as Embedding -from .reranking import Reranking as Reranking +from .embedding_response import EmbeddingResponse as EmbeddingResponse +from .reranking_response import RerankingResponse as RerankingResponse from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .reranking_create_params import RerankingCreateParams as RerankingCreateParams diff --git a/src/isaacus/types/classifications/__init__.py b/src/isaacus/types/classifications/__init__.py index da75725..ed68aaf 100644 --- a/src/isaacus/types/classifications/__init__.py +++ b/src/isaacus/types/classifications/__init__.py @@ -3,4 +3,4 @@ from __future__ import annotations from .universal_create_params import UniversalCreateParams as UniversalCreateParams -from .universal_classification import UniversalClassification as UniversalClassification +from .universal_classification_response import UniversalClassificationResponse as UniversalClassificationResponse diff --git a/src/isaacus/types/classifications/universal_classification.py b/src/isaacus/types/classifications/universal_classification_response.py similarity index 94% rename from src/isaacus/types/classifications/universal_classification.py rename to src/isaacus/types/classifications/universal_classification_response.py index 5674c28..2dd9920 100644 --- a/src/isaacus/types/classifications/universal_classification.py +++ b/src/isaacus/types/classifications/universal_classification_response.py @@ -4,7 +4,7 @@ from ..._models import BaseModel -__all__ = ["UniversalClassification", "Classification", "ClassificationChunk", "Usage"] +__all__ = ["UniversalClassificationResponse", "Classification", "ClassificationChunk", "Usage"] class ClassificationChunk(BaseModel): @@ -72,7 +72,7 @@ class Usage(BaseModel): """The number of tokens inputted to the model.""" -class UniversalClassification(BaseModel): +class UniversalClassificationResponse(BaseModel): classifications: List[Classification] """ The classifications of the texts, by relevance to the query, in order from diff --git a/src/isaacus/types/embedding.py b/src/isaacus/types/embedding_response.py similarity index 54% rename from src/isaacus/types/embedding.py rename to src/isaacus/types/embedding_response.py index ddc8bec..bcd16fe 100644 --- a/src/isaacus/types/embedding.py +++ b/src/isaacus/types/embedding_response.py @@ -4,7 +4,18 @@ from .._models import BaseModel -__all__ = ["Embedding", "Usage"] +__all__ = ["EmbeddingResponse", "Embedding", "Usage"] + + +class Embedding(BaseModel): + embedding: List[float] + """The embedding of the content represented as an array of floating point numbers.""" + + index: int + """ + The position of the content in the input array of contents, starting from `0` + (and, therefore, ending at the number of contents minus `1`). + """ class Usage(BaseModel): @@ -12,7 +23,7 @@ class Usage(BaseModel): """The number of tokens inputted to the model.""" -class Embedding(BaseModel): +class EmbeddingResponse(BaseModel): embeddings: List[Embedding] """The embeddings of the inputs.""" diff --git a/src/isaacus/types/extractions/__init__.py b/src/isaacus/types/extractions/__init__.py index ab167e2..39de7b6 100644 --- a/src/isaacus/types/extractions/__init__.py +++ b/src/isaacus/types/extractions/__init__.py @@ -3,4 +3,4 @@ from __future__ import annotations from .qa_create_params import QaCreateParams as QaCreateParams -from .answer_extraction import AnswerExtraction as AnswerExtraction +from .answer_extraction_response import AnswerExtractionResponse as AnswerExtractionResponse diff --git a/src/isaacus/types/extractions/answer_extraction.py b/src/isaacus/types/extractions/answer_extraction_response.py similarity index 94% rename from src/isaacus/types/extractions/answer_extraction.py rename to src/isaacus/types/extractions/answer_extraction_response.py index 28c519c..1466a62 100644 --- a/src/isaacus/types/extractions/answer_extraction.py +++ b/src/isaacus/types/extractions/answer_extraction_response.py @@ -4,7 +4,7 @@ from ..._models import BaseModel -__all__ = ["AnswerExtraction", "Extraction", "ExtractionAnswer", "Usage"] +__all__ = ["AnswerExtractionResponse", "Extraction", "ExtractionAnswer", "Usage"] class ExtractionAnswer(BaseModel): @@ -57,7 +57,7 @@ class Usage(BaseModel): """The number of tokens inputted to the model.""" -class AnswerExtraction(BaseModel): +class AnswerExtractionResponse(BaseModel): extractions: List[Extraction] """ The results of extracting answers from the texts, ordered from highest to lowest diff --git a/src/isaacus/types/reranking.py b/src/isaacus/types/reranking_response.py similarity index 90% rename from src/isaacus/types/reranking.py rename to src/isaacus/types/reranking_response.py index 9d147e0..71cc1ce 100644 --- a/src/isaacus/types/reranking.py +++ b/src/isaacus/types/reranking_response.py @@ -4,7 +4,7 @@ from .._models import BaseModel -__all__ = ["Reranking", "Result", "Usage"] +__all__ = ["RerankingResponse", "Result", "Usage"] class Result(BaseModel): @@ -26,7 +26,7 @@ class Usage(BaseModel): """The number of tokens inputted to the model.""" -class Reranking(BaseModel): +class RerankingResponse(BaseModel): results: List[Result] """ The rerankings of the texts, by relevance to the query, in order from highest to diff --git a/tests/api_resources/classifications/test_universal.py b/tests/api_resources/classifications/test_universal.py index 892e6b4..48de376 100644 --- a/tests/api_resources/classifications/test_universal.py +++ b/tests/api_resources/classifications/test_universal.py @@ -9,7 +9,7 @@ from isaacus import Isaacus, AsyncIsaacus from tests.utils import assert_matches_type -from isaacus.types.classifications import UniversalClassification +from isaacus.types.classifications import UniversalClassificationResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -25,7 +25,7 @@ def test_method_create(self, client: Isaacus) -> None: query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], ) - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -42,7 +42,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: is_iql=True, scoring_method="auto", ) - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -56,7 +56,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" universal = response.parse() - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -70,7 +70,7 @@ def test_streaming_response_create(self, client: Isaacus) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" universal = response.parse() - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) assert cast(Any, response.is_closed) is True @@ -88,7 +88,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: query="This is a confidentiality clause.", texts=["I agree not to tell anyone about the document."], ) - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -105,7 +105,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - is_iql=True, scoring_method="auto", ) - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -119,7 +119,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" universal = await response.parse() - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -133,6 +133,6 @@ async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" universal = await response.parse() - assert_matches_type(UniversalClassification, universal, path=["response"]) + assert_matches_type(UniversalClassificationResponse, universal, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/extractions/test_qa.py b/tests/api_resources/extractions/test_qa.py index 798f00d..181cfce 100644 --- a/tests/api_resources/extractions/test_qa.py +++ b/tests/api_resources/extractions/test_qa.py @@ -9,7 +9,7 @@ from isaacus import Isaacus, AsyncIsaacus from tests.utils import assert_matches_type -from isaacus.types.extractions import AnswerExtraction +from isaacus.types.extractions import AnswerExtractionResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -27,7 +27,7 @@ def test_method_create(self, client: Isaacus) -> None: "The standard sentence for murder in the State of Victoria is 30 years if the person murdered was a police officer and 25 years in any other case." ], ) - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -46,7 +46,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: ignore_inextractability=False, top_k=1, ) - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -62,7 +62,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" qa = response.parse() - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -78,7 +78,7 @@ def test_streaming_response_create(self, client: Isaacus) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" qa = response.parse() - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) assert cast(Any, response.is_closed) is True @@ -98,7 +98,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: "The standard sentence for murder in the State of Victoria is 30 years if the person murdered was a police officer and 25 years in any other case." ], ) - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -117,7 +117,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - ignore_inextractability=False, top_k=1, ) - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -133,7 +133,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" qa = await response.parse() - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -149,6 +149,6 @@ async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" qa = await response.parse() - assert_matches_type(AnswerExtraction, qa, path=["response"]) + assert_matches_type(AnswerExtractionResponse, qa, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py index 45cfc6a..abab3cc 100644 --- a/tests/api_resources/test_embeddings.py +++ b/tests/api_resources/test_embeddings.py @@ -9,7 +9,7 @@ from isaacus import Isaacus, AsyncIsaacus from tests.utils import assert_matches_type -from isaacus.types import Embedding +from isaacus.types import EmbeddingResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -24,7 +24,7 @@ def test_method_create(self, client: Isaacus) -> None: model="kanon-2-embedder", texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -36,7 +36,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: overflow_strategy="drop_end", task="retrieval/query", ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -49,7 +49,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -62,7 +62,7 @@ def test_streaming_response_create(self, client: Isaacus) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) assert cast(Any, response.is_closed) is True @@ -79,7 +79,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: model="kanon-2-embedder", texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -91,7 +91,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - overflow_strategy="drop_end", task="retrieval/query", ) - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -104,7 +104,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = await response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -117,6 +117,6 @@ async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = await response.parse() - assert_matches_type(Embedding, embedding, path=["response"]) + assert_matches_type(EmbeddingResponse, embedding, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_rerankings.py b/tests/api_resources/test_rerankings.py index 38bfa44..a9d965b 100644 --- a/tests/api_resources/test_rerankings.py +++ b/tests/api_resources/test_rerankings.py @@ -9,7 +9,7 @@ from isaacus import Isaacus, AsyncIsaacus from tests.utils import assert_matches_type -from isaacus.types import Reranking +from isaacus.types import RerankingResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -31,7 +31,7 @@ def test_method_create(self, client: Isaacus) -> None: "The concept of negligence is central to tort law, with courts assessing whether a breach of duty caused harm.", ], ) - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -55,7 +55,7 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: scoring_method="auto", top_n=1, ) - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -75,7 +75,7 @@ def test_raw_response_create(self, client: Isaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" reranking = response.parse() - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -95,7 +95,7 @@ def test_streaming_response_create(self, client: Isaacus) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" reranking = response.parse() - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) assert cast(Any, response.is_closed) is True @@ -119,7 +119,7 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: "The concept of negligence is central to tort law, with courts assessing whether a breach of duty caused harm.", ], ) - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -143,7 +143,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - scoring_method="auto", top_n=1, ) - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -163,7 +163,7 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" reranking = await response.parse() - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -183,6 +183,6 @@ async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" reranking = await response.parse() - assert_matches_type(Reranking, reranking, path=["response"]) + assert_matches_type(RerankingResponse, reranking, path=["response"]) assert cast(Any, response.is_closed) is True From 0ad7114b5fec2fde9aaa830a6ba6163ad3b6fccc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 06:06:16 +0000 Subject: [PATCH 35/38] feat(api)!: reduce max length of embeddings input --- .stats.yml | 4 ++-- src/isaacus/resources/embeddings.py | 4 ++-- src/isaacus/types/embedding_create_params.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index d798ff2..98c78c0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-43557cfcc912900321433e1b633b39a81a3bfdbcfc86784aff4ff8219f5ebec4.yml -openapi_spec_hash: e612b47282935484c5de2ebbd49df824 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-38797afacaa32899534343a428aa51ad45a9440ce299aa8356d7056f75605ac9.yml +openapi_spec_hash: 73a38488ff341eae312b04e8aeeafaa0 config_hash: efa2ea406c5ecd6883ff8b0fb428e579 diff --git a/src/isaacus/resources/embeddings.py b/src/isaacus/resources/embeddings.py index 3ab5f18..96f0c51 100644 --- a/src/isaacus/resources/embeddings.py +++ b/src/isaacus/resources/embeddings.py @@ -70,7 +70,7 @@ def create( Each text must contain at least one non-whitespace character. - No more than 1,000 texts can be embedded in a single request. + No more than 128 texts can be embedded in a single request. dimensions: A whole number greater than or equal to 1. @@ -163,7 +163,7 @@ async def create( Each text must contain at least one non-whitespace character. - No more than 1,000 texts can be embedded in a single request. + No more than 128 texts can be embedded in a single request. dimensions: A whole number greater than or equal to 1. diff --git a/src/isaacus/types/embedding_create_params.py b/src/isaacus/types/embedding_create_params.py index 3600291..2db7c07 100644 --- a/src/isaacus/types/embedding_create_params.py +++ b/src/isaacus/types/embedding_create_params.py @@ -22,7 +22,7 @@ class EmbeddingCreateParams(TypedDict, total=False): Each text must contain at least one non-whitespace character. - No more than 1,000 texts can be embedded in a single request. + No more than 128 texts can be embedded in a single request. """ dimensions: Optional[int] From 5d4a1b99e8a6ac2a1c3cc4e83e7b65108eea335a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 03:13:36 +0000 Subject: [PATCH 36/38] fix(api): typo --- .stats.yml | 4 ++-- src/isaacus/resources/extractions/qa.py | 10 ++++++---- src/isaacus/types/extractions/qa_create_params.py | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 98c78c0..81a8244 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-38797afacaa32899534343a428aa51ad45a9440ce299aa8356d7056f75605ac9.yml -openapi_spec_hash: 73a38488ff341eae312b04e8aeeafaa0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-ee884a4336559147aacf9a927a540f21e9760f00d2d5588af00fa8a25e2707d9.yml +openapi_spec_hash: 2ba78bd360942c63a7d08dba791f00d2 config_hash: efa2ea406c5ecd6883ff8b0fb428e579 diff --git a/src/isaacus/resources/extractions/qa.py b/src/isaacus/resources/extractions/qa.py index b1ed8d7..22168f3 100644 --- a/src/isaacus/resources/extractions/qa.py +++ b/src/isaacus/resources/extractions/qa.py @@ -65,8 +65,9 @@ def create( answer extractor. Args: - model: The ID of the [model](https://docs.isaacus.com/models#extractive-qa) to use for - extractive question answering. + model: The ID of the + [model](https://docs.isaacus.com/models#extractive-question-answering) to use + for extractive question answering. query: The query to extract the answer to. @@ -164,8 +165,9 @@ async def create( answer extractor. Args: - model: The ID of the [model](https://docs.isaacus.com/models#extractive-qa) to use for - extractive question answering. + model: The ID of the + [model](https://docs.isaacus.com/models#extractive-question-answering) to use + for extractive question answering. query: The query to extract the answer to. diff --git a/src/isaacus/types/extractions/qa_create_params.py b/src/isaacus/types/extractions/qa_create_params.py index 85b34b2..867d3d1 100644 --- a/src/isaacus/types/extractions/qa_create_params.py +++ b/src/isaacus/types/extractions/qa_create_params.py @@ -13,8 +13,9 @@ class QaCreateParams(TypedDict, total=False): model: Required[Literal["kanon-answer-extractor", "kanon-answer-extractor-mini"]] """ - The ID of the [model](https://docs.isaacus.com/models#extractive-qa) to use for - extractive question answering. + The ID of the + [model](https://docs.isaacus.com/models#extractive-question-answering) to use + for extractive question answering. """ query: Required[str] From caa70f7acf6ce910d8cf80425437ca51970cd255 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 06:34:40 +0000 Subject: [PATCH 37/38] docs(sdk): make embeddings example first --- .stats.yml | 2 +- README.md | 87 ++++++++++++++++++++++++------------------ tests/test_client.py | 90 ++++++++++++++++++++------------------------ 3 files changed, 91 insertions(+), 88 deletions(-) diff --git a/.stats.yml b/.stats.yml index 81a8244..0987f00 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 4 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-ee884a4336559147aacf9a927a540f21e9760f00d2d5588af00fa8a25e2707d9.yml openapi_spec_hash: 2ba78bd360942c63a7d08dba791f00d2 -config_hash: efa2ea406c5ecd6883ff8b0fb428e579 +config_hash: a85580968a69d8d6fadf96e5e2d6870e diff --git a/README.md b/README.md index 6718e1c..818160e 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,14 @@ client = Isaacus( api_key=os.environ.get("ISAACUS_API_KEY"), # This is the default and can be omitted ) -universal_classification_response = client.classifications.universal.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], +embedding_response = client.embeddings.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) -print(universal_classification_response.classifications) +print(embedding_response.embeddings) ``` While you can provide an `api_key` keyword argument, @@ -60,12 +62,14 @@ client = AsyncIsaacus( async def main() -> None: - universal_classification_response = await client.classifications.universal.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + embedding_response = await client.embeddings.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) - print(universal_classification_response.classifications) + print(embedding_response.embeddings) asyncio.run(main()) @@ -97,12 +101,14 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - universal_classification_response = await client.classifications.universal.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + embedding_response = await client.embeddings.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) - print(universal_classification_response.classifications) + print(embedding_response.embeddings) asyncio.run(main()) @@ -155,10 +161,12 @@ from isaacus import Isaacus client = Isaacus() try: - client.classifications.universal.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + client.embeddings.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) except isaacus.APIConnectionError as e: print("The server could not be reached") @@ -202,10 +210,12 @@ client = Isaacus( ) # Or, configure per-request: -client.with_options(max_retries=5).classifications.universal.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], +client.with_options(max_retries=5).embeddings.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) ``` @@ -229,10 +239,12 @@ client = Isaacus( ) # Override per-request: -client.with_options(timeout=5.0).classifications.universal.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], +client.with_options(timeout=5.0).embeddings.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) ``` @@ -274,15 +286,14 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from isaacus import Isaacus client = Isaacus() -response = client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], +response = client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) print(response.headers.get('X-My-Header')) -universal = response.parse() # get the object that `classifications.universal.create()` would have returned -print(universal.classifications) +embedding = response.parse() # get the object that `embeddings.create()` would have returned +print(embedding.embeddings) ``` These methods return an [`APIResponse`](https://github.com/isaacus-dev/isaacus-python/tree/main/src/isaacus/_response.py) object. @@ -296,10 +307,12 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.classifications.universal.with_streaming_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], +with client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=[ + "Are restraints of trade enforceable under English law?", + "What is a non-compete clause?", + ], ) as response: print(response.headers.get("X-My-Header")) diff --git a/tests/test_client.py b/tests/test_client.py index dcb7d53..0478e5f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -712,13 +712,12 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("isaacus._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Isaacus) -> None: - respx_mock.post("/classifications/universal").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/embeddings").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.classifications.universal.with_streaming_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ).__enter__() assert _get_open_connections(self.client) == 0 @@ -726,13 +725,12 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien @mock.patch("isaacus._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Isaacus) -> None: - respx_mock.post("/classifications/universal").mock(return_value=httpx.Response(500)) + respx_mock.post("/embeddings").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.classifications.universal.with_streaming_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ).__enter__() assert _get_open_connections(self.client) == 0 @@ -760,12 +758,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) + respx_mock.post("/embeddings").mock(side_effect=retry_handler) - response = client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + response = client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) assert response.retries_taken == failures_before_success @@ -788,12 +785,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) + respx_mock.post("/embeddings").mock(side_effect=retry_handler) - response = client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + response = client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -816,12 +812,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) + respx_mock.post("/embeddings").mock(side_effect=retry_handler) - response = client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + response = client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], extra_headers={"x-stainless-retry-count": "42"}, ) @@ -1551,13 +1546,12 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte async def test_retrying_timeout_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncIsaacus ) -> None: - respx_mock.post("/classifications/universal").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/embeddings").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.classifications.universal.with_streaming_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + await async_client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ).__aenter__() assert _get_open_connections(self.client) == 0 @@ -1565,13 +1559,12 @@ async def test_retrying_timeout_errors_doesnt_leak( @mock.patch("isaacus._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncIsaacus) -> None: - respx_mock.post("/classifications/universal").mock(return_value=httpx.Response(500)) + respx_mock.post("/embeddings").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.classifications.universal.with_streaming_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + await async_client.embeddings.with_streaming_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ).__aenter__() assert _get_open_connections(self.client) == 0 @@ -1600,12 +1593,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) + respx_mock.post("/embeddings").mock(side_effect=retry_handler) - response = await client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + response = await client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], ) assert response.retries_taken == failures_before_success @@ -1629,12 +1621,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) + respx_mock.post("/embeddings").mock(side_effect=retry_handler) - response = await client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + response = await client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -1658,12 +1649,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) + respx_mock.post("/embeddings").mock(side_effect=retry_handler) - response = await client.classifications.universal.with_raw_response.create( - model="kanon-universal-classifier", - query="This is a confidentiality clause.", - texts=["I agree not to tell anyone about the document."], + response = await client.embeddings.with_raw_response.create( + model="kanon-2-embedder", + texts=["Are restraints of trade enforceable under English law?", "What is a non-compete clause?"], extra_headers={"x-stainless-retry-count": "42"}, ) From 2c43b6c20fd551cd0aa66f7d1f0b3ac895ff6b73 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 06:35:05 +0000 Subject: [PATCH 38/38] release: 0.9.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 55 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/isaacus/_version.py | 2 +- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6538ca9..6d78745 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.8.0" + ".": "0.9.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f0e038..660ac06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## 0.9.0 (2025-10-14) + +Full Changelog: [v0.8.0...v0.9.0](https://github.com/isaacus-dev/isaacus-python/compare/v0.8.0...v0.9.0) + +### ⚠ BREAKING CHANGES + +* **api:** reduce max length of embeddings input +* **sdk:** add `_response` to response models to finally fix duplicated names + +### Features + +* **api:** added embedding endpoint ([88190d6](https://github.com/isaacus-dev/isaacus-python/commit/88190d6d33c8d5e3cf59dfd3c488b5ae9abec93b)) +* **api:** reduce max length of embeddings input ([0ad7114](https://github.com/isaacus-dev/isaacus-python/commit/0ad7114b5fec2fde9aaa830a6ba6163ad3b6fccc)) +* **api:** rename embedding -> embeddings ([204a05d](https://github.com/isaacus-dev/isaacus-python/commit/204a05d7b1504901766db3c0d0d8ea47a22a16ed)) +* **api:** revert embedding -> embeddings ([b934279](https://github.com/isaacus-dev/isaacus-python/commit/b9342795e50374817b8e3dc2e2f1163a2ff0805a)) +* **client:** support file upload requests ([2ab398d](https://github.com/isaacus-dev/isaacus-python/commit/2ab398dde07e98411c9b6efd76f7b7120a9633a8)) +* improve future compat with pydantic v3 ([5a20497](https://github.com/isaacus-dev/isaacus-python/commit/5a20497a9c4bbf88056df12a0c686566dc9bd162)) +* **sdk:** add embeddings endpoint ([920ae0b](https://github.com/isaacus-dev/isaacus-python/commit/920ae0b65f2362ac098f8b94979b1e821f5143d8)) +* **sdk:** toggle to force regen ([cf60482](https://github.com/isaacus-dev/isaacus-python/commit/cf60482ba0dd3933daee477fa9bd4ae29d900fb4)) +* **sdk:** untoggle to force regen ([25d2067](https://github.com/isaacus-dev/isaacus-python/commit/25d2067fad4bb46ca595001f6e82458fd3d24a23)) +* **types:** replace List[str] with SequenceNotStr in params ([d2733a9](https://github.com/isaacus-dev/isaacus-python/commit/d2733a9d0f16531537a9db017a8e29d2c8fb3912)) + + +### Bug Fixes + +* **api:** typo ([5d4a1b9](https://github.com/isaacus-dev/isaacus-python/commit/5d4a1b99e8a6ac2a1c3cc4e83e7b65108eea335a)) +* avoid newer type syntax ([10253fe](https://github.com/isaacus-dev/isaacus-python/commit/10253fe93ed8142b52cf5199486221e81ac6ce5a)) +* **sdk:** add `_response` to response models to finally fix duplicated names ([5c7462d](https://github.com/isaacus-dev/isaacus-python/commit/5c7462dd25c67c44126eb946a656a6b841dc6a50)) + + +### Chores + +* **api:** try to force regen SDK ([2fafb55](https://github.com/isaacus-dev/isaacus-python/commit/2fafb555c1a20d7c359c91c35fd1f54868cffe54)) +* do not install brew dependencies in ./scripts/bootstrap by default ([57b055e](https://github.com/isaacus-dev/isaacus-python/commit/57b055ed56fdcc58b4663e4ddad32afac25e7ec1)) +* improve example values ([35b03bd](https://github.com/isaacus-dev/isaacus-python/commit/35b03bdbf4ceaccd00102e23d639a01d5bea136a)) +* **internal:** add Sequence related utils ([5a2287e](https://github.com/isaacus-dev/isaacus-python/commit/5a2287ef854d250048c070f3fd88b00ca84b0d3c)) +* **internal:** change ci workflow machines ([f86cbce](https://github.com/isaacus-dev/isaacus-python/commit/f86cbcef2583658466e95eaba4aba61f79646ef9)) +* **internal:** codegen related update ([22b520b](https://github.com/isaacus-dev/isaacus-python/commit/22b520b3c67e570f9267135111a89542ee2bdf7f)) +* **internal:** fix ruff target version ([889d576](https://github.com/isaacus-dev/isaacus-python/commit/889d576cdc28d06404c6ee3ce0c67bf4d3be75c4)) +* **internal:** move mypy configurations to `pyproject.toml` file ([d5732d5](https://github.com/isaacus-dev/isaacus-python/commit/d5732d5e0145763723e8be24cbd8296f9a385264)) +* **internal:** update comment in script ([7af966e](https://github.com/isaacus-dev/isaacus-python/commit/7af966e1677b44d412eda96c5ee8e9866f77ccfb)) +* **internal:** update pydantic dependency ([68a7057](https://github.com/isaacus-dev/isaacus-python/commit/68a70578a2e269fa3b2c46e3c29e82ba770090d6)) +* **internal:** update pyright exclude list ([6f0ae86](https://github.com/isaacus-dev/isaacus-python/commit/6f0ae86899883fe77aa669d595c623bedc2dc5c8)) +* remove custom code ([491dbdc](https://github.com/isaacus-dev/isaacus-python/commit/491dbdcd82984d099b8ee11e94894ad450b2424d)) +* **sdk:** restore original example ([079645e](https://github.com/isaacus-dev/isaacus-python/commit/079645e85259c2e4d3f6aa86b2ca2c21ce97367a)) +* **tests:** simplify `get_platform` test ([e00ccd0](https://github.com/isaacus-dev/isaacus-python/commit/e00ccd0c41c3751eb3fae880223ebb05eae0f154)) +* **types:** change optional parameter type from NotGiven to Omit ([38d13e0](https://github.com/isaacus-dev/isaacus-python/commit/38d13e0514b001d1a34446b881783d559e246865)) +* update @stainless-api/prism-cli to v5.15.0 ([a3141f5](https://github.com/isaacus-dev/isaacus-python/commit/a3141f59b0ff6334fde2a9740fd2f86824fe5083)) +* update github action ([0518028](https://github.com/isaacus-dev/isaacus-python/commit/05180288265bc111dba1c62fbfcd90139a6299ad)) + + +### Documentation + +* **sdk:** make embeddings example first ([caa70f7](https://github.com/isaacus-dev/isaacus-python/commit/caa70f7acf6ce910d8cf80425437ca51970cd255)) + ## 0.8.0 (2025-07-25) Full Changelog: [v0.7.0...v0.8.0](https://github.com/isaacus-dev/isaacus-python/compare/v0.7.0...v0.8.0) diff --git a/pyproject.toml b/pyproject.toml index 1c54c43..28bc3fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "isaacus" -version = "0.8.0" +version = "0.9.0" description = "The official Python library for the isaacus API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/isaacus/_version.py b/src/isaacus/_version.py index 1bf0197..19eec74 100644 --- a/src/isaacus/_version.py +++ b/src/isaacus/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "isaacus" -__version__ = "0.8.0" # x-release-please-version +__version__ = "0.9.0" # x-release-please-version