diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac9a2e7..55d2025 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -6,4 +6,4 @@ USER vscode RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH -RUN echo "[[ -d .venv ]] && source .venv/bin/activate" >> /home/vscode/.bashrc +RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bbeb30b..c17fdc1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,6 +24,9 @@ } } } + }, + "features": { + "ghcr.io/devcontainers/features/node:1": {} } // Features to add to the dev container. More info: https://containers.dev/features. diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml deleted file mode 100644 index 44c5863..0000000 --- a/.github/workflows/create-releases.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Create releases -on: - schedule: - - cron: '0 5 * * *' # every day at 5am UTC - push: - branches: - - main - -jobs: - release: - name: release - if: github.ref == 'refs/heads/main' && github.repository == 'isaacus-dev/isaacus-python' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: stainless-api/trigger-release-please@v1 - id: release - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Install Rye - if: ${{ steps.release.outputs.releases_created }} - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.35.0' - RYE_INSTALL_OPTION: '--yes' - - - name: Publish to PyPI - if: ${{ steps.release.outputs.releases_created }} - run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.ISAACUS_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 10506f9..31d3cc0 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,9 +1,13 @@ -# workflow for re-running publishing to PyPI in case it fails for some reason -# you can run this workflow by navigating to https://www.github.com/isaacus-dev/isaacus-python/actions/workflows/publish-pypi.yml +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to PyPI in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/isaacus-dev/isaacus-python/actions/workflows/publish-pypi.yml name: Publish PyPI on: workflow_dispatch: + release: + types: [published] + jobs: publish: name: publish diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index ff1fee4..e1eed33 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -18,5 +18,4 @@ jobs: run: | bash ./bin/check-release-environment env: - STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} PYPI_TOKEN: ${{ secrets.ISAACUS_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c476280..ba6c348 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1-alpha.0" + ".": "0.1.0-alpha.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 0460849..0fca039 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 1 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-874c20d494cb37fc2f1c78b089cf16065bd2910c0fcb4dcc89cc55b73a3d6fc4.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/isaacus%2Fisaacus-5f5feba7202b37bb046fcbe0389f011c14816d03a3d5c2a008d9736bff840f1a.yml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..328f887 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ +# Changelog + +## 0.1.0-alpha.1 (2025-03-04) + +Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/isaacus-dev/isaacus-python/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) + +### Features + +* added latest OpenAPI specification ([#1](https://github.com/isaacus-dev/isaacus-python/issues/1)) ([ee4cdd8](https://github.com/isaacus-dev/isaacus-python/commit/ee4cdd8df312a81d4a46da568ff2a37d55127f28)) +* added latest OpenAPI specification ([#3](https://github.com/isaacus-dev/isaacus-python/issues/3)) ([e6234c7](https://github.com/isaacus-dev/isaacus-python/commit/e6234c71a201beb74666d0ef7f7077a686f4a690)) +* **api:** added latest OpenAPI specification ([#13](https://github.com/isaacus-dev/isaacus-python/issues/13)) ([822a5b5](https://github.com/isaacus-dev/isaacus-python/commit/822a5b561b88de0a7aaca05f786bffaeab16371a)) +* **api:** added latest OpenAPI specification ([#4](https://github.com/isaacus-dev/isaacus-python/issues/4)) ([8841b6a](https://github.com/isaacus-dev/isaacus-python/commit/8841b6a28bde24db83c08a864ab3d8aef9007cfa)) +* **api:** added latest OpenAPI specification ([#5](https://github.com/isaacus-dev/isaacus-python/issues/5)) ([36f1cd8](https://github.com/isaacus-dev/isaacus-python/commit/36f1cd8f3ebb1abaedbe8b0a4e19c8747011f9f3)) +* **api:** added latest OpenAPI specification ([#8](https://github.com/isaacus-dev/isaacus-python/issues/8)) ([0ba3728](https://github.com/isaacus-dev/isaacus-python/commit/0ba3728aa0c7509e344f1c5029ecc54ade403266)) +* **api:** update via SDK Studio ([2863c6c](https://github.com/isaacus-dev/isaacus-python/commit/2863c6c6f72258b53649f63cc8cb2e4f480f4818)) +* **client:** allow passing `NotGiven` for body ([#6](https://github.com/isaacus-dev/isaacus-python/issues/6)) ([539267b](https://github.com/isaacus-dev/isaacus-python/commit/539267b95ab1a193db15ba46dd2fed6d67b994c9)) + + +### Bug Fixes + +* asyncify on non-asyncio runtimes ([268752f](https://github.com/isaacus-dev/isaacus-python/commit/268752f5baa48fff9ebd30ed739cc5765f43dab1)) +* **client:** mark some request bodies as optional ([539267b](https://github.com/isaacus-dev/isaacus-python/commit/539267b95ab1a193db15ba46dd2fed6d67b994c9)) + + +### Chores + +* **docs:** update client docstring ([#11](https://github.com/isaacus-dev/isaacus-python/issues/11)) ([bb860bc](https://github.com/isaacus-dev/isaacus-python/commit/bb860bc18a916cd707b709bff17e2510973623b5)) +* **internal:** fix devcontainers setup ([#7](https://github.com/isaacus-dev/isaacus-python/issues/7)) ([23046c4](https://github.com/isaacus-dev/isaacus-python/commit/23046c49e639ee760e9206e99c3e13baaf5d6b30)) +* **internal:** properly set __pydantic_private__ ([#9](https://github.com/isaacus-dev/isaacus-python/issues/9)) ([16c7d5e](https://github.com/isaacus-dev/isaacus-python/commit/16c7d5e011fbb479ff0ba5bc850fc76cabd682cd)) +* **internal:** remove unused http client options forwarding ([#12](https://github.com/isaacus-dev/isaacus-python/issues/12)) ([af1ee9e](https://github.com/isaacus-dev/isaacus-python/commit/af1ee9e77d51cbd053d3e48e9adf80f243fb19a5)) +* **internal:** update client tests ([ac65c8f](https://github.com/isaacus-dev/isaacus-python/commit/ac65c8f3b45159cd75f14466249e524679c1481d)) +* update SDK settings ([#14](https://github.com/isaacus-dev/isaacus-python/issues/14)) ([4d87849](https://github.com/isaacus-dev/isaacus-python/commit/4d878496b4ae774ec92e4bc08f26a708b698685d)) + + +### Documentation + +* update URLs from stainlessapi.com to stainless.com ([#10](https://github.com/isaacus-dev/isaacus-python/issues/10)) ([7e625b2](https://github.com/isaacus-dev/isaacus-python/commit/7e625b262c4e480379ddbe5bd2ca983f83c90988)) diff --git a/README.md b/README.md index 09129c3..f8bdd2e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Isaacus Python library provides convenient access to the Isaacus REST API fr application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). -It is generated with [Stainless](https://www.stainlessapi.com/). +It is generated with [Stainless](https://www.stainless.com/). ## Documentation @@ -15,13 +15,10 @@ The REST API documentation can be found on [docs.isaacus.com](https://docs.isaac ## Installation ```sh -# install from the production repo -pip install git+ssh://git@github.com/isaacus-dev/isaacus-python.git +# install from PyPI +pip install --pre isaacus ``` -> [!NOTE] -> Once this package is [published to PyPI](https://app.stainlessapi.com/docs/guides/publish), this will become: `pip install --pre isaacus` - ## Usage The full API of this library can be found in [api.md](api.md). @@ -31,21 +28,21 @@ import os from isaacus import Isaacus client = Isaacus( - bearer_token=os.environ.get("ISAACUS_API_KEY"), # This is the default and can be omitted + api_key=os.environ.get("ISAACUS_API_KEY"), # This is the default and can be omitted ) universal_classification = client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) print(universal_classification.chunks) ``` -While you can provide a `bearer_token` keyword argument, +While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `ISAACUS_API_KEY="My Bearer Token"` to your `.env` file -so that your Bearer Token is not stored in source control. +to add `ISAACUS_API_KEY="My API Key"` to your `.env` file +so that your API Key is not stored in source control. ## Async usage @@ -57,15 +54,15 @@ import asyncio from isaacus import AsyncIsaacus client = AsyncIsaacus( - bearer_token=os.environ.get("ISAACUS_API_KEY"), # This is the default and can be omitted + api_key=os.environ.get("ISAACUS_API_KEY"), # This is the default and can be omitted ) async def main() -> None: universal_classification = await client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) print(universal_classification.chunks) @@ -101,9 +98,9 @@ client = Isaacus() try: client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) except isaacus.APIConnectionError as e: print("The server could not be reached") @@ -148,9 +145,9 @@ client = Isaacus( # Or, configure per-request: client.with_options(max_retries=5).classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) ``` @@ -175,9 +172,9 @@ client = Isaacus( # Override per-request: client.with_options(timeout=5.0).classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) ``` @@ -220,9 +217,9 @@ from isaacus import Isaacus client = Isaacus() response = client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) print(response.headers.get('X-My-Header')) @@ -242,9 +239,9 @@ To stream the response body, use `.with_streaming_response` instead, which requi ```python with client.classifications.universal.with_streaming_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) as response: print(response.headers.get("X-My-Header")) diff --git a/SECURITY.md b/SECURITY.md index a4d85c5..8657a37 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure diff --git a/bin/check-release-environment b/bin/check-release-environment index db40a55..70c1ff7 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,10 +2,6 @@ errors=() -if [ -z "${STAINLESS_API_KEY}" ]; then - errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") -fi - if [ -z "${PYPI_TOKEN}" ]; then errors+=("The ISAACUS_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi diff --git a/pyproject.toml b/pyproject.toml index b821309..ea2a3d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "isaacus" -version = "0.0.1-alpha.0" +version = "0.1.0-alpha.1" description = "The official Python library for the isaacus API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/isaacus/_base_client.py b/src/isaacus/_base_client.py index d43c437..7bff76a 100644 --- a/src/isaacus/_base_client.py +++ b/src/isaacus/_base_client.py @@ -9,7 +9,6 @@ import inspect import logging import platform -import warnings import email.utils from types import TracebackType from random import random @@ -36,7 +35,7 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions @@ -51,19 +50,16 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping -from ._compat import model_copy, model_dump +from ._compat import PYDANTIC_V2, model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, @@ -207,6 +203,9 @@ def _set_private_attributes( model: Type[_T], options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -292,6 +291,9 @@ def _set_private_attributes( client: AsyncAPIClient, options: FinalRequestOptions, ) -> None: + if PYDANTIC_V2 and getattr(self, "__pydantic_private__", None) is None: + self.__pydantic_private__ = {} + self._model = model self._client = client self._options = options @@ -331,9 +333,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -346,9 +345,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -356,9 +352,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -518,7 +511,7 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data, + json=json_data if is_given(json_data) else None, files=files, **kwargs, ) @@ -794,46 +787,11 @@ def __init__( base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -854,12 +812,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -869,9 +824,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1366,45 +1318,10 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1426,11 +1343,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1440,9 +1354,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: diff --git a/src/isaacus/_client.py b/src/isaacus/_client.py index b38fd30..d2b679f 100644 --- a/src/isaacus/_client.py +++ b/src/isaacus/_client.py @@ -42,12 +42,12 @@ class Isaacus(SyncAPIClient): with_streaming_response: IsaacusWithStreamedResponse # client options - bearer_token: str + api_key: str def __init__( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -67,17 +67,17 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous isaacus client instance. + """Construct a new synchronous Isaacus client instance. - This automatically infers the `bearer_token` argument from the `ISAACUS_API_KEY` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `ISAACUS_API_KEY` environment variable if it is not provided. """ - if bearer_token is None: - bearer_token = os.environ.get("ISAACUS_API_KEY") - if bearer_token is None: + if api_key is None: + api_key = os.environ.get("ISAACUS_API_KEY") + if api_key is None: raise IsaacusError( - "The bearer_token client option must be set either by passing bearer_token to the client or by setting the ISAACUS_API_KEY environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the ISAACUS_API_KEY environment variable" ) - self.bearer_token = bearer_token + self.api_key = api_key if base_url is None: base_url = os.environ.get("ISAACUS_BASE_URL") @@ -107,8 +107,8 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - bearer_token = self.bearer_token - return {"Authorization": f"Bearer {bearer_token}"} + api_key = self.api_key + return {"Authorization": f"Bearer {api_key}"} @property @override @@ -122,7 +122,7 @@ def default_headers(self) -> dict[str, str | Omit]: def copy( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.Client | None = None, @@ -156,7 +156,7 @@ def copy( http_client = http_client or self._client return self.__class__( - bearer_token=bearer_token or self.bearer_token, + api_key=api_key or self.api_key, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -210,12 +210,12 @@ class AsyncIsaacus(AsyncAPIClient): with_streaming_response: AsyncIsaacusWithStreamedResponse # client options - bearer_token: str + api_key: str def __init__( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -235,17 +235,17 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async isaacus client instance. + """Construct a new async AsyncIsaacus client instance. - This automatically infers the `bearer_token` argument from the `ISAACUS_API_KEY` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `ISAACUS_API_KEY` environment variable if it is not provided. """ - if bearer_token is None: - bearer_token = os.environ.get("ISAACUS_API_KEY") - if bearer_token is None: + if api_key is None: + api_key = os.environ.get("ISAACUS_API_KEY") + if api_key is None: raise IsaacusError( - "The bearer_token client option must be set either by passing bearer_token to the client or by setting the ISAACUS_API_KEY environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the ISAACUS_API_KEY environment variable" ) - self.bearer_token = bearer_token + self.api_key = api_key if base_url is None: base_url = os.environ.get("ISAACUS_BASE_URL") @@ -275,8 +275,8 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - bearer_token = self.bearer_token - return {"Authorization": f"Bearer {bearer_token}"} + api_key = self.api_key + return {"Authorization": f"Bearer {api_key}"} @property @override @@ -290,7 +290,7 @@ def default_headers(self) -> dict[str, str | Omit]: def copy( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.AsyncClient | None = None, @@ -324,7 +324,7 @@ def copy( http_client = http_client or self._client return self.__class__( - bearer_token=bearer_token or self.bearer_token, + api_key=api_key or self.api_key, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, diff --git a/src/isaacus/_version.py b/src/isaacus/_version.py index 007e1e1..7fd40c6 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.0.1-alpha.0" # x-release-please-version +__version__ = "0.1.0-alpha.1" # x-release-please-version diff --git a/src/isaacus/resources/classifications/universal.py b/src/isaacus/resources/classifications/universal.py index 623f5e1..b7445e8 100644 --- a/src/isaacus/resources/classifications/universal.py +++ b/src/isaacus/resources/classifications/universal.py @@ -50,7 +50,7 @@ def with_streaming_response(self) -> UniversalResourceWithStreamingResponse: def create( self, *, - model: Literal["kanon-uniclassifier", "kanon-uniclassifier-mini"], + model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, text: str, chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, @@ -64,7 +64,8 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> UniversalClassification: """ - Universal classification + Classify the relevance of a legal document to a query using an Isaacus universal + legal AI classifier. Args: model: The ID of the model to use for universal classification. @@ -72,8 +73,15 @@ def create( query: The Isaacus Query Language (IQL) query or, if IQL is disabled, the statement, to evaluate the text against. + The query must contain at least one non-whitespace character. + + Unlike the text being classified, the query cannot be so long that it exceeds + the maximum input length of the universal classifier. + text: The text to classify. + The text must contain at least one non-whitespace character. + chunking_options: Options for how to split text into smaller chunks. is_iql: Whether the query should be interpreted as an Isaacus Query Language (IQL) query @@ -142,7 +150,7 @@ def with_streaming_response(self) -> AsyncUniversalResourceWithStreamingResponse async def create( self, *, - model: Literal["kanon-uniclassifier", "kanon-uniclassifier-mini"], + model: Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"], query: str, text: str, chunking_options: Optional[universal_create_params.ChunkingOptions] | NotGiven = NOT_GIVEN, @@ -156,7 +164,8 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> UniversalClassification: """ - Universal classification + Classify the relevance of a legal document to a query using an Isaacus universal + legal AI classifier. Args: model: The ID of the model to use for universal classification. @@ -164,8 +173,15 @@ async def create( query: The Isaacus Query Language (IQL) query or, if IQL is disabled, the statement, to evaluate the text against. + The query must contain at least one non-whitespace character. + + Unlike the text being classified, the query cannot be so long that it exceeds + the maximum input length of the universal classifier. + text: The text to classify. + The text must contain at least one non-whitespace character. + chunking_options: Options for how to split text into smaller chunks. is_iql: Whether the query should be interpreted as an Isaacus Query Language (IQL) query diff --git a/src/isaacus/types/classifications/universal_classification.py b/src/isaacus/types/classifications/universal_classification.py index 3d1e192..fd5c58e 100644 --- a/src/isaacus/types/classifications/universal_classification.py +++ b/src/isaacus/types/classifications/universal_classification.py @@ -8,7 +8,10 @@ class Chunk(BaseModel): - confidence: float + end: int + """The end index of the chunk in the original text.""" + + score: float """ The model's score of the likelihood that the query expressed about the chunk is supported by the chunk. @@ -17,9 +20,6 @@ class Chunk(BaseModel): score less than `0.5` indicates that the chunk does not support the query. """ - end: int - """The end index of the chunk in the original text.""" - start: int """The start index of the chunk in the original text.""" @@ -34,7 +34,10 @@ class Usage(BaseModel): class UniversalClassification(BaseModel): chunks: Optional[List[Chunk]] = None - """The text broken into chunks, each with their own confidence score. + """ + The text as broken into chunks by + [semchunk](https://github.com/isaacus-dev/semchunk), each chunk with its own + confidence score. If no chunking occurred, this will be `null`. """ @@ -49,4 +52,4 @@ class UniversalClassification(BaseModel): """ usage: Usage - """Statistics about the usage of resources in the process of classifying a text.""" + """Statistics about the usage of resources in the process of classifying the text.""" diff --git a/src/isaacus/types/classifications/universal_create_params.py b/src/isaacus/types/classifications/universal_create_params.py index c349209..63fa143 100644 --- a/src/isaacus/types/classifications/universal_create_params.py +++ b/src/isaacus/types/classifications/universal_create_params.py @@ -9,17 +9,25 @@ class UniversalCreateParams(TypedDict, total=False): - model: Required[Literal["kanon-uniclassifier", "kanon-uniclassifier-mini"]] + model: Required[Literal["kanon-universal-classifier", "kanon-universal-classifier-mini"]] """The ID of the model to use for universal classification.""" query: Required[str] """ The Isaacus Query Language (IQL) query or, if IQL is disabled, the statement, to evaluate the text against. + + The query must contain at least one non-whitespace character. + + Unlike the text being classified, the query cannot be so long that it exceeds + the maximum input length of the universal classifier. """ text: Required[str] - """The text to classify.""" + """The text to classify. + + The text must contain at least one non-whitespace character. + """ chunking_options: Optional[ChunkingOptions] """Options for how to split text into smaller chunks.""" diff --git a/tests/api_resources/classifications/test_universal.py b/tests/api_resources/classifications/test_universal.py index 3835f26..ff7fe4d 100644 --- a/tests/api_resources/classifications/test_universal.py +++ b/tests/api_resources/classifications/test_universal.py @@ -21,9 +21,9 @@ class TestUniversal: @parametrize def test_method_create(self, client: Isaacus) -> None: universal = client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) assert_matches_type(UniversalClassification, universal, path=["response"]) @@ -31,9 +31,9 @@ def test_method_create(self, client: Isaacus) -> None: @parametrize def test_method_create_with_all_params(self, client: Isaacus) -> None: universal = client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", chunking_options={ "overlap_ratio": 0.1, "overlap_tokens": 0, @@ -48,9 +48,9 @@ def test_method_create_with_all_params(self, client: Isaacus) -> None: @parametrize def test_raw_response_create(self, client: Isaacus) -> None: response = client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) assert response.is_closed is True @@ -62,9 +62,9 @@ def test_raw_response_create(self, client: Isaacus) -> None: @parametrize def test_streaming_response_create(self, client: Isaacus) -> None: with client.classifications.universal.with_streaming_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -82,9 +82,9 @@ class TestAsyncUniversal: @parametrize async def test_method_create(self, async_client: AsyncIsaacus) -> None: universal = await async_client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) assert_matches_type(UniversalClassification, universal, path=["response"]) @@ -92,9 +92,9 @@ async def test_method_create(self, async_client: AsyncIsaacus) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) -> None: universal = await async_client.classifications.universal.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", chunking_options={ "overlap_ratio": 0.1, "overlap_tokens": 0, @@ -109,9 +109,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncIsaacus) - @parametrize async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: response = await async_client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) assert response.is_closed is True @@ -123,9 +123,9 @@ async def test_raw_response_create(self, async_client: AsyncIsaacus) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncIsaacus) -> None: async with async_client.classifications.universal.with_streaming_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/conftest.py b/tests/conftest.py index 24c65a2..bdae75a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -bearer_token = "My Bearer Token" +api_key = "My API Key" @pytest.fixture(scope="session") @@ -37,7 +37,7 @@ def client(request: FixtureRequest) -> Iterator[Isaacus]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict) as client: + with Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: yield client @@ -47,5 +47,5 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncIsaacus]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict) as client: + async with AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index ac3f2d0..4130c98 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -38,7 +38,7 @@ from .utils import update_env base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -bearer_token = "My Bearer Token" +api_key = "My API Key" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -60,7 +60,7 @@ def _get_open_connections(client: Isaacus | AsyncIsaacus) -> int: class TestIsaacus: - client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -86,9 +86,9 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(bearer_token="another My Bearer Token") - assert copied.bearer_token == "another My Bearer Token" - assert self.client.bearer_token == "My Bearer Token" + copied = self.client.copy(api_key="another My API Key") + assert copied.api_key == "another My API Key" + assert self.client.api_key == "My API Key" def test_copy_default_options(self) -> None: # options that have a default are overridden correctly @@ -108,10 +108,7 @@ def test_copy_default_options(self) -> None: def test_copy_default_headers(self) -> None: client = Isaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) assert client.default_headers["X-Foo"] == "bar" @@ -145,7 +142,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = Isaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -269,9 +266,7 @@ def test_request_timeout(self) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = Isaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0) - ) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -281,7 +276,7 @@ def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: client = Isaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -291,7 +286,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = Isaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -301,7 +296,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = Isaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -313,17 +308,14 @@ async def test_invalid_http_client(self) -> None: async with httpx.AsyncClient() as http_client: Isaacus( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: client = Isaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" @@ -331,7 +323,7 @@ def test_default_headers_option(self) -> None: client2 = Isaacus( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -343,21 +335,18 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_validate_headers(self) -> None: - client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {bearer_token}" + assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(IsaacusError): with update_env(**{"ISAACUS_API_KEY": Omit()}): - client2 = Isaacus(base_url=base_url, bearer_token=None, _strict_response_validation=True) + client2 = Isaacus(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: client = Isaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_query={"query_param": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -557,9 +546,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Isaacus( - base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True - ) + client = Isaacus(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -568,20 +555,16 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(ISAACUS_BASE_URL="http://localhost:5000/from/env"): - client = Isaacus(bearer_token=bearer_token, _strict_response_validation=True) + client = Isaacus(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ + Isaacus(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Isaacus( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, - ), - Isaacus( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -601,14 +584,10 @@ def test_base_url_trailing_slash(self, client: Isaacus) -> None: @pytest.mark.parametrize( "client", [ + Isaacus(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Isaacus( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, - ), - Isaacus( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -628,14 +607,10 @@ def test_base_url_no_trailing_slash(self, client: Isaacus) -> None: @pytest.mark.parametrize( "client", [ + Isaacus(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Isaacus( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, - ), - Isaacus( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -653,7 +628,7 @@ def test_absolute_request_url(self, client: Isaacus) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -664,7 +639,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -685,12 +660,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Isaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - max_retries=cast(Any, None), - ) + Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -699,12 +669,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + strict_client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -732,7 +702,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Isaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Isaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -751,9 +721,9 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No object, maybe_transform( dict( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ), UniversalCreateParams, ), @@ -776,9 +746,9 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non object, maybe_transform( dict( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ), UniversalCreateParams, ), @@ -816,9 +786,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) response = client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) assert response.retries_taken == failures_before_success @@ -844,9 +814,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) response = client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -872,9 +842,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) response = client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", extra_headers={"x-stainless-retry-count": "42"}, ) @@ -882,7 +852,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: class TestAsyncIsaacus: - client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -910,9 +880,9 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(bearer_token="another My Bearer Token") - assert copied.bearer_token == "another My Bearer Token" - assert self.client.bearer_token == "My Bearer Token" + copied = self.client.copy(api_key="another My API Key") + assert copied.api_key == "another My API Key" + assert self.client.api_key == "My API Key" def test_copy_default_options(self) -> None: # options that have a default are overridden correctly @@ -932,10 +902,7 @@ def test_copy_default_options(self) -> None: def test_copy_default_headers(self) -> None: client = AsyncIsaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) assert client.default_headers["X-Foo"] == "bar" @@ -969,7 +936,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = AsyncIsaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -1094,7 +1061,7 @@ async def test_request_timeout(self) -> None: async def test_client_timeout_option(self) -> None: client = AsyncIsaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1105,7 +1072,7 @@ async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: client = AsyncIsaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1115,7 +1082,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncIsaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1125,7 +1092,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncIsaacus( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1137,17 +1104,14 @@ def test_invalid_http_client(self) -> None: with httpx.Client() as http_client: AsyncIsaacus( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: client = AsyncIsaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" @@ -1155,7 +1119,7 @@ def test_default_headers_option(self) -> None: client2 = AsyncIsaacus( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1167,21 +1131,18 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_validate_headers(self) -> None: - client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {bearer_token}" + assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(IsaacusError): with update_env(**{"ISAACUS_API_KEY": Omit()}): - client2 = AsyncIsaacus(base_url=base_url, bearer_token=None, _strict_response_validation=True) + client2 = AsyncIsaacus(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: client = AsyncIsaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_query={"query_param": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -1382,7 +1343,7 @@ class Model(BaseModel): def test_base_url_setter(self) -> None: client = AsyncIsaacus( - base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True + base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) assert client.base_url == "https://example.com/from_init/" @@ -1392,20 +1353,18 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(ISAACUS_BASE_URL="http://localhost:5000/from/env"): - client = AsyncIsaacus(bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncIsaacus(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ AsyncIsaacus( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), AsyncIsaacus( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1426,13 +1385,11 @@ def test_base_url_trailing_slash(self, client: AsyncIsaacus) -> None: "client", [ AsyncIsaacus( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), AsyncIsaacus( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1453,13 +1410,11 @@ def test_base_url_no_trailing_slash(self, client: AsyncIsaacus) -> None: "client", [ AsyncIsaacus( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), AsyncIsaacus( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1477,7 +1432,7 @@ def test_absolute_request_url(self, client: AsyncIsaacus) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1489,7 +1444,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1512,10 +1467,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): AsyncIsaacus( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - max_retries=cast(Any, None), + base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None) ) @pytest.mark.respx(base_url=base_url) @@ -1526,12 +1478,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + strict_client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False) + client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1560,7 +1512,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncIsaacus(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncIsaacus(base_url=base_url, api_key=api_key, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -1579,9 +1531,9 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) object, maybe_transform( dict( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ), UniversalCreateParams, ), @@ -1604,9 +1556,9 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) object, maybe_transform( dict( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ), UniversalCreateParams, ), @@ -1645,9 +1597,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) response = await client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", ) assert response.retries_taken == failures_before_success @@ -1674,9 +1626,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) response = await client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -1703,9 +1655,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/classifications/universal").mock(side_effect=retry_handler) response = await client.classifications.universal.with_raw_response.create( - model="kanon-uniclassifier", + model="kanon-universal-classifier", query="This is a confidentiality clause.", - text="The Supplier agrees not to disclose to any person, other than the Customer, any Confidential Information relating to the Contract or the Goods and/or Services, without prior written approval from the Customer.", + text="I agree not to tell anyone about the document.", extra_headers={"x-stainless-retry-count": "42"}, )