diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3cec802..cd3bfb0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,7 +19,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/alchemyst-ai-sdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -44,7 +44,7 @@ jobs:
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/alchemyst-ai-sdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
@@ -81,7 +81,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/alchemyst-ai-sdk-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 2d8d220..2eabb25 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Install Rye
run: |
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 78d40bc..44d3d6a 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'Alchemyst-ai/alchemyst-sdk-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Check release environment
run: |
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 6d78745..091cfb1 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.9.0"
+ ".": "0.10.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 6571c4d..7a1ec0c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 11
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/alchemyst-ai%2Falchemyst-ai-sdk-e8e44e0e972cd2e130110834826053d128e088f8c94fa824b4e8f0498cc3ff7a.yml
-openapi_spec_hash: 23b2524dd290e587b543601546731842
-config_hash: 5aa39dd4797ce0dacbe19c69fcf11be6
+configured_endpoints: 15
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/alchemyst-ai%2Falchemyst-ai-sdk-af43a0360c784fd31239bbacd7343fefe06847df4706d18ddf0d1778ef597e80.yml
+openapi_spec_hash: 3e6daacc7aa9b60ca68fdb7b6f76fbf9
+config_hash: 32e700129a8eaba38674e583e3d39e2b
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c09ff89..533fc42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# Changelog
+## 0.10.0 (2026-01-17)
+
+Full Changelog: [v0.9.0...v0.10.0](https://github.com/Alchemyst-ai/alchemyst-sdk-python/compare/v0.9.0...v0.10.0)
+
+### Features
+
+* **api:** api update ([938b01f](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/938b01ff0092d7ac349bf0c2978ac5feccd8bd68))
+* **api:** api update ([04c852d](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/04c852da6d40f9b8ca4b157d978134cf4db180b5))
+* **api:** api update ([6b3b044](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/6b3b0445a1d1920cc732b29155718f4c80a44b46))
+* **api:** api update ([7d5b6e4](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/7d5b6e4828f2e010412e1a6947303d0282500fd6))
+* **api:** manual updates ([a502f74](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/a502f74122c3183ff20d0146547349e9ecd062fc))
+* **api:** manual updates ([628e068](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/628e068d6f4ced825a7d90a9a7730fecdfc8984a))
+* **client:** add support for binary request streaming ([020ff9a](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/020ff9a9078ffa02c7407fe73e07d508ab1f7b5c))
+
+
+### Bug Fixes
+
+* **client:** loosen auth header validation ([3d175cb](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/3d175cb24d4ec289e8654eb2dcf4685ddb2a17af))
+
+
+### Chores
+
+* **internal:** update `actions/checkout` version ([11aa112](https://github.com/Alchemyst-ai/alchemyst-sdk-python/commit/11aa11229f32a79c7e061f805c64918b3cc409f5))
+
## 0.9.0 (2026-01-06)
Full Changelog: [v0.8.1...v0.9.0](https://github.com/Alchemyst-ai/alchemyst-sdk-python/compare/v0.8.1...v0.9.0)
diff --git a/README.md b/README.md
index 0f70e7a..125221a 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ It is generated with [Stainless](https://www.stainless.com/).
## Documentation
-The REST API documentation can be found on [docs.getalchemystai.com](https://docs.getalchemystai.com). The full API of this library can be found in [api.md](api.md).
+The REST API documentation can be found on [getalchemystai.com](https://getalchemystai.com/docs). The full API of this library can be found in [api.md](api.md).
## Installation
diff --git a/api.md b/api.md
index a5e2d22..9605b3f 100644
--- a/api.md
+++ b/api.md
@@ -54,6 +54,32 @@ Methods:
- client.v1.context.memory.delete(\*\*params) -> None
- client.v1.context.memory.add(\*\*params) -> MemoryAddResponse
+### AddAsync
+
+Types:
+
+```python
+from alchemyst_ai.types.v1.context import AddAsyncCreateResponse, AddAsyncCancelResponse
+```
+
+Methods:
+
+- client.v1.context.add_async.create(\*\*params) -> AddAsyncCreateResponse
+- client.v1.context.add_async.cancel(id) -> AddAsyncCancelResponse
+
+#### Status
+
+Types:
+
+```python
+from alchemyst_ai.types.v1.context.add_async import StatusRetrieveResponse, StatusListResponse
+```
+
+Methods:
+
+- client.v1.context.add_async.status.retrieve(id) -> StatusRetrieveResponse
+- client.v1.context.add_async.status.list(\*\*params) -> StatusListResponse
+
## Org
### Context
diff --git a/pyproject.toml b/pyproject.toml
index fc3fd2d..418b5ab 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "alchemystai"
-version = "0.9.0"
+version = "0.10.0"
description = "The official Python library for the alchemyst_ai API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/alchemyst_ai/_base_client.py b/src/alchemyst_ai/_base_client.py
index 424dbd9..93ef387 100644
--- a/src/alchemyst_ai/_base_client.py
+++ b/src/alchemyst_ai/_base_client.py
@@ -9,6 +9,7 @@
import inspect
import logging
import platform
+import warnings
import email.utils
from types import TracebackType
from random import random
@@ -51,9 +52,11 @@
ResponseT,
AnyMapping,
PostParser,
+ BinaryTypes,
RequestFiles,
HttpxSendArgs,
RequestOptions,
+ AsyncBinaryTypes,
HttpxRequestFiles,
ModelBuilderProtocol,
not_given,
@@ -477,8 +480,19 @@ def _build_request(
retries_taken: int = 0,
) -> httpx.Request:
if log.isEnabledFor(logging.DEBUG):
- log.debug("Request options: %s", model_dump(options, exclude_unset=True))
-
+ log.debug(
+ "Request options: %s",
+ model_dump(
+ options,
+ exclude_unset=True,
+ # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
+ exclude={
+ "content",
+ }
+ if PYDANTIC_V1
+ else {},
+ ),
+ )
kwargs: dict[str, Any] = {}
json_data = options.json_data
@@ -532,7 +546,13 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"
if is_body_allowed:
- if isinstance(json_data, bytes):
+ if options.content is not None and json_data is not None:
+ raise TypeError("Passing both `content` and `json_data` is not supported")
+ if options.content is not None and files is not None:
+ raise TypeError("Passing both `content` and `files` is not supported")
+ if options.content is not None:
+ kwargs["content"] = options.content
+ elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
@@ -1194,6 +1214,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[False] = False,
@@ -1206,6 +1227,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: Literal[True],
@@ -1219,6 +1241,7 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool,
@@ -1231,13 +1254,25 @@ def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
files: RequestFiles | None = None,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
@@ -1247,11 +1282,23 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1261,11 +1308,23 @@ def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put", url=path, json_data=body, files=to_httpx_files(files), **options
+ method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
return self.request(cast_to, opts)
@@ -1275,9 +1334,19 @@ def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: BinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return self.request(cast_to, opts)
def get_api_list(
@@ -1717,6 +1786,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[False] = False,
@@ -1729,6 +1799,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: Literal[True],
@@ -1742,6 +1813,7 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool,
@@ -1754,13 +1826,25 @@ async def post(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
) -> ResponseT | _AsyncStreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
@@ -1770,11 +1854,28 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="patch",
+ url=path,
+ json_data=body,
+ content=content,
+ files=await async_to_httpx_files(files),
+ **options,
)
return await self.request(cast_to, opts)
@@ -1784,11 +1885,23 @@ async def put(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
opts = FinalRequestOptions.construct(
- method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
return await self.request(cast_to, opts)
@@ -1798,9 +1911,19 @@ async def delete(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options)
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
return await self.request(cast_to, opts)
def get_api_list(
diff --git a/src/alchemyst_ai/_client.py b/src/alchemyst_ai/_client.py
index 37b2393..67f63b1 100644
--- a/src/alchemyst_ai/_client.py
+++ b/src/alchemyst_ai/_client.py
@@ -136,9 +136,7 @@ def default_headers(self) -> dict[str, str | Omit]:
@override
def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
- if self.api_key and headers.get("Authorization"):
- return
- if isinstance(custom_headers.get("Authorization"), Omit):
+ if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
return
raise TypeError(
@@ -319,9 +317,7 @@ def default_headers(self) -> dict[str, str | Omit]:
@override
def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None:
- if self.api_key and headers.get("Authorization"):
- return
- if isinstance(custom_headers.get("Authorization"), Omit):
+ if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit):
return
raise TypeError(
diff --git a/src/alchemyst_ai/_models.py b/src/alchemyst_ai/_models.py
index ca9500b..29070e0 100644
--- a/src/alchemyst_ai/_models.py
+++ b/src/alchemyst_ai/_models.py
@@ -3,7 +3,20 @@
import os
import inspect
import weakref
-from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Type,
+ Union,
+ Generic,
+ TypeVar,
+ Callable,
+ Iterable,
+ Optional,
+ AsyncIterable,
+ cast,
+)
from datetime import date, datetime
from typing_extensions import (
List,
@@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
json_data: Body
extra_json: AnyMapping
follow_redirects: bool
@@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel):
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
follow_redirects: Union[bool, None] = None
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
json_data: Union[Body, None] = None
diff --git a/src/alchemyst_ai/_types.py b/src/alchemyst_ai/_types.py
index b10f73b..74039ed 100644
--- a/src/alchemyst_ai/_types.py
+++ b/src/alchemyst_ai/_types.py
@@ -13,9 +13,11 @@
Mapping,
TypeVar,
Callable,
+ Iterable,
Iterator,
Optional,
Sequence,
+ AsyncIterable,
)
from typing_extensions import (
Set,
@@ -56,6 +58,13 @@
else:
Base64FileInput = Union[IO[bytes], PathLike]
FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.
+
+
+# Used for sending raw binary data / streaming data in request bodies
+# e.g. for file uploads without multipart encoding
+BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
+AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]
+
FileTypes = Union[
# file (or bytes)
FileContent,
diff --git a/src/alchemyst_ai/_version.py b/src/alchemyst_ai/_version.py
index 6263ed6..c11ca7b 100644
--- a/src/alchemyst_ai/_version.py
+++ b/src/alchemyst_ai/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "alchemyst_ai"
-__version__ = "0.9.0" # x-release-please-version
+__version__ = "0.10.0" # x-release-please-version
diff --git a/src/alchemyst_ai/resources/v1/context/__init__.py b/src/alchemyst_ai/resources/v1/context/__init__.py
index 236a4b4..cb39ab2 100644
--- a/src/alchemyst_ai/resources/v1/context/__init__.py
+++ b/src/alchemyst_ai/resources/v1/context/__init__.py
@@ -32,6 +32,14 @@
ContextResourceWithStreamingResponse,
AsyncContextResourceWithStreamingResponse,
)
+from .add_async import (
+ AddAsyncResource,
+ AsyncAddAsyncResource,
+ AddAsyncResourceWithRawResponse,
+ AsyncAddAsyncResourceWithRawResponse,
+ AddAsyncResourceWithStreamingResponse,
+ AsyncAddAsyncResourceWithStreamingResponse,
+)
__all__ = [
"TracesResource",
@@ -52,6 +60,12 @@
"AsyncMemoryResourceWithRawResponse",
"MemoryResourceWithStreamingResponse",
"AsyncMemoryResourceWithStreamingResponse",
+ "AddAsyncResource",
+ "AsyncAddAsyncResource",
+ "AddAsyncResourceWithRawResponse",
+ "AsyncAddAsyncResourceWithRawResponse",
+ "AddAsyncResourceWithStreamingResponse",
+ "AsyncAddAsyncResourceWithStreamingResponse",
"ContextResource",
"AsyncContextResource",
"ContextResourceWithRawResponse",
diff --git a/src/alchemyst_ai/resources/v1/context/add_async/__init__.py b/src/alchemyst_ai/resources/v1/context/add_async/__init__.py
new file mode 100644
index 0000000..f425106
--- /dev/null
+++ b/src/alchemyst_ai/resources/v1/context/add_async/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .status import (
+ StatusResource,
+ AsyncStatusResource,
+ StatusResourceWithRawResponse,
+ AsyncStatusResourceWithRawResponse,
+ StatusResourceWithStreamingResponse,
+ AsyncStatusResourceWithStreamingResponse,
+)
+from .add_async import (
+ AddAsyncResource,
+ AsyncAddAsyncResource,
+ AddAsyncResourceWithRawResponse,
+ AsyncAddAsyncResourceWithRawResponse,
+ AddAsyncResourceWithStreamingResponse,
+ AsyncAddAsyncResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "StatusResource",
+ "AsyncStatusResource",
+ "StatusResourceWithRawResponse",
+ "AsyncStatusResourceWithRawResponse",
+ "StatusResourceWithStreamingResponse",
+ "AsyncStatusResourceWithStreamingResponse",
+ "AddAsyncResource",
+ "AsyncAddAsyncResource",
+ "AddAsyncResourceWithRawResponse",
+ "AsyncAddAsyncResourceWithRawResponse",
+ "AddAsyncResourceWithStreamingResponse",
+ "AsyncAddAsyncResourceWithStreamingResponse",
+]
diff --git a/src/alchemyst_ai/resources/v1/context/add_async/add_async.py b/src/alchemyst_ai/resources/v1/context/add_async/add_async.py
new file mode 100644
index 0000000..6810950
--- /dev/null
+++ b/src/alchemyst_ai/resources/v1/context/add_async/add_async.py
@@ -0,0 +1,337 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal
+
+import httpx
+
+from .status import (
+ StatusResource,
+ AsyncStatusResource,
+ StatusResourceWithRawResponse,
+ AsyncStatusResourceWithRawResponse,
+ StatusResourceWithStreamingResponse,
+ AsyncStatusResourceWithStreamingResponse,
+)
+from ....._types import Body, Omit, Query, Headers, NotGiven, 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.v1.context import add_async_create_params
+from .....types.v1.context.add_async_cancel_response import AddAsyncCancelResponse
+from .....types.v1.context.add_async_create_response import AddAsyncCreateResponse
+
+__all__ = ["AddAsyncResource", "AsyncAddAsyncResource"]
+
+
+class AddAsyncResource(SyncAPIResource):
+ @cached_property
+ def status(self) -> StatusResource:
+ return StatusResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AddAsyncResourceWithRawResponse:
+ """
+ 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/Alchemyst-ai/alchemyst-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AddAsyncResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AddAsyncResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Alchemyst-ai/alchemyst-sdk-python#with_streaming_response
+ """
+ return AddAsyncResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ context_type: Literal["resource", "conversation", "instruction"],
+ documents: Iterable[add_async_create_params.Document],
+ scope: Literal["internal", "external"],
+ source: str,
+ metadata: add_async_create_params.Metadata | 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,
+ ) -> AddAsyncCreateResponse:
+ """
+ This endpoint accepts context data and queues it for asynchronous processing by
+ the context processor. It returns a success or error response depending on the
+ queuing result.
+
+ Args:
+ context_type: Type of context being added
+
+ documents: Array of documents with content and additional metadata
+
+ scope: Scope of the context
+
+ source: The source of the context data
+
+ metadata: Additional metadata for the context
+
+ 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(
+ "/api/v1/context/add-async",
+ body=maybe_transform(
+ {
+ "context_type": context_type,
+ "documents": documents,
+ "scope": scope,
+ "source": source,
+ "metadata": metadata,
+ },
+ add_async_create_params.AddAsyncCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AddAsyncCreateResponse,
+ )
+
+ def cancel(
+ self,
+ id: str,
+ *,
+ # 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,
+ ) -> AddAsyncCancelResponse:
+ """
+ Attempts to cancel a context add job by job id.
+
+ - If the job is already completed or failed, returns 404.
+ - If the job is currently running ("active"), returns 409 and cannot be
+ cancelled.
+ - Only jobs in "waiting" or "delayed" state can be cancelled.
+
+ Args:
+ 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
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._delete(
+ f"/api/v1/context/add-async/{id}/cancel",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AddAsyncCancelResponse,
+ )
+
+
+class AsyncAddAsyncResource(AsyncAPIResource):
+ @cached_property
+ def status(self) -> AsyncStatusResource:
+ return AsyncStatusResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAddAsyncResourceWithRawResponse:
+ """
+ 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/Alchemyst-ai/alchemyst-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncAddAsyncResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAddAsyncResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Alchemyst-ai/alchemyst-sdk-python#with_streaming_response
+ """
+ return AsyncAddAsyncResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ context_type: Literal["resource", "conversation", "instruction"],
+ documents: Iterable[add_async_create_params.Document],
+ scope: Literal["internal", "external"],
+ source: str,
+ metadata: add_async_create_params.Metadata | 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,
+ ) -> AddAsyncCreateResponse:
+ """
+ This endpoint accepts context data and queues it for asynchronous processing by
+ the context processor. It returns a success or error response depending on the
+ queuing result.
+
+ Args:
+ context_type: Type of context being added
+
+ documents: Array of documents with content and additional metadata
+
+ scope: Scope of the context
+
+ source: The source of the context data
+
+ metadata: Additional metadata for the context
+
+ 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(
+ "/api/v1/context/add-async",
+ body=await async_maybe_transform(
+ {
+ "context_type": context_type,
+ "documents": documents,
+ "scope": scope,
+ "source": source,
+ "metadata": metadata,
+ },
+ add_async_create_params.AddAsyncCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AddAsyncCreateResponse,
+ )
+
+ async def cancel(
+ self,
+ id: str,
+ *,
+ # 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,
+ ) -> AddAsyncCancelResponse:
+ """
+ Attempts to cancel a context add job by job id.
+
+ - If the job is already completed or failed, returns 404.
+ - If the job is currently running ("active"), returns 409 and cannot be
+ cancelled.
+ - Only jobs in "waiting" or "delayed" state can be cancelled.
+
+ Args:
+ 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
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._delete(
+ f"/api/v1/context/add-async/{id}/cancel",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AddAsyncCancelResponse,
+ )
+
+
+class AddAsyncResourceWithRawResponse:
+ def __init__(self, add_async: AddAsyncResource) -> None:
+ self._add_async = add_async
+
+ self.create = to_raw_response_wrapper(
+ add_async.create,
+ )
+ self.cancel = to_raw_response_wrapper(
+ add_async.cancel,
+ )
+
+ @cached_property
+ def status(self) -> StatusResourceWithRawResponse:
+ return StatusResourceWithRawResponse(self._add_async.status)
+
+
+class AsyncAddAsyncResourceWithRawResponse:
+ def __init__(self, add_async: AsyncAddAsyncResource) -> None:
+ self._add_async = add_async
+
+ self.create = async_to_raw_response_wrapper(
+ add_async.create,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ add_async.cancel,
+ )
+
+ @cached_property
+ def status(self) -> AsyncStatusResourceWithRawResponse:
+ return AsyncStatusResourceWithRawResponse(self._add_async.status)
+
+
+class AddAsyncResourceWithStreamingResponse:
+ def __init__(self, add_async: AddAsyncResource) -> None:
+ self._add_async = add_async
+
+ self.create = to_streamed_response_wrapper(
+ add_async.create,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ add_async.cancel,
+ )
+
+ @cached_property
+ def status(self) -> StatusResourceWithStreamingResponse:
+ return StatusResourceWithStreamingResponse(self._add_async.status)
+
+
+class AsyncAddAsyncResourceWithStreamingResponse:
+ def __init__(self, add_async: AsyncAddAsyncResource) -> None:
+ self._add_async = add_async
+
+ self.create = async_to_streamed_response_wrapper(
+ add_async.create,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ add_async.cancel,
+ )
+
+ @cached_property
+ def status(self) -> AsyncStatusResourceWithStreamingResponse:
+ return AsyncStatusResourceWithStreamingResponse(self._add_async.status)
diff --git a/src/alchemyst_ai/resources/v1/context/add_async/status.py b/src/alchemyst_ai/resources/v1/context/add_async/status.py
new file mode 100644
index 0000000..8979ac1
--- /dev/null
+++ b/src/alchemyst_ai/resources/v1/context/add_async/status.py
@@ -0,0 +1,282 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ....._types import Body, Omit, Query, Headers, NotGiven, 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.v1.context.add_async import status_list_params
+from .....types.v1.context.add_async.status_list_response import StatusListResponse
+from .....types.v1.context.add_async.status_retrieve_response import StatusRetrieveResponse
+
+__all__ = ["StatusResource", "AsyncStatusResource"]
+
+
+class StatusResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> StatusResourceWithRawResponse:
+ """
+ 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/Alchemyst-ai/alchemyst-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return StatusResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> StatusResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Alchemyst-ai/alchemyst-sdk-python#with_streaming_response
+ """
+ return StatusResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ id: str,
+ *,
+ # 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,
+ ) -> StatusRetrieveResponse:
+ """
+ Returns the status and result of a context add job by job id.
+
+ Args:
+ 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
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ f"/api/v1/context/add-async/{id}/status",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=StatusRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ limit: str | Omit = omit,
+ offset: str | Omit = omit,
+ type: Literal["all", "active", "failed", "completed"] | 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,
+ ) -> StatusListResponse:
+ """
+ Returns all jobs (active, waiting, delayed, failed, completed) belonging to the
+ authenticated user.
+
+ Args:
+ limit: Maximum number of jobs to return
+
+ offset: Number of jobs to skip before starting to collect the result set
+
+ type: Type of jobs to list
+
+ 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._get(
+ "/api/v1/context/add-async/status",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ "type": type,
+ },
+ status_list_params.StatusListParams,
+ ),
+ ),
+ cast_to=StatusListResponse,
+ )
+
+
+class AsyncStatusResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncStatusResourceWithRawResponse:
+ """
+ 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/Alchemyst-ai/alchemyst-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncStatusResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncStatusResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/Alchemyst-ai/alchemyst-sdk-python#with_streaming_response
+ """
+ return AsyncStatusResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ id: str,
+ *,
+ # 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,
+ ) -> StatusRetrieveResponse:
+ """
+ Returns the status and result of a context add job by job id.
+
+ Args:
+ 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
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ f"/api/v1/context/add-async/{id}/status",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=StatusRetrieveResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ limit: str | Omit = omit,
+ offset: str | Omit = omit,
+ type: Literal["all", "active", "failed", "completed"] | 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,
+ ) -> StatusListResponse:
+ """
+ Returns all jobs (active, waiting, delayed, failed, completed) belonging to the
+ authenticated user.
+
+ Args:
+ limit: Maximum number of jobs to return
+
+ offset: Number of jobs to skip before starting to collect the result set
+
+ type: Type of jobs to list
+
+ 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._get(
+ "/api/v1/context/add-async/status",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ "type": type,
+ },
+ status_list_params.StatusListParams,
+ ),
+ ),
+ cast_to=StatusListResponse,
+ )
+
+
+class StatusResourceWithRawResponse:
+ def __init__(self, status: StatusResource) -> None:
+ self._status = status
+
+ self.retrieve = to_raw_response_wrapper(
+ status.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ status.list,
+ )
+
+
+class AsyncStatusResourceWithRawResponse:
+ def __init__(self, status: AsyncStatusResource) -> None:
+ self._status = status
+
+ self.retrieve = async_to_raw_response_wrapper(
+ status.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ status.list,
+ )
+
+
+class StatusResourceWithStreamingResponse:
+ def __init__(self, status: StatusResource) -> None:
+ self._status = status
+
+ self.retrieve = to_streamed_response_wrapper(
+ status.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ status.list,
+ )
+
+
+class AsyncStatusResourceWithStreamingResponse:
+ def __init__(self, status: AsyncStatusResource) -> None:
+ self._status = status
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ status.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ status.list,
+ )
diff --git a/src/alchemyst_ai/resources/v1/context/context.py b/src/alchemyst_ai/resources/v1/context/context.py
index 359c195..ccdc934 100644
--- a/src/alchemyst_ai/resources/v1/context/context.py
+++ b/src/alchemyst_ai/resources/v1/context/context.py
@@ -43,6 +43,14 @@
async_to_streamed_response_wrapper,
)
from ...._base_client import make_request_options
+from .add_async.add_async import (
+ AddAsyncResource,
+ AsyncAddAsyncResource,
+ AddAsyncResourceWithRawResponse,
+ AsyncAddAsyncResourceWithRawResponse,
+ AddAsyncResourceWithStreamingResponse,
+ AsyncAddAsyncResourceWithStreamingResponse,
+)
from ....types.v1.context_add_response import ContextAddResponse
from ....types.v1.context_search_response import ContextSearchResponse
@@ -62,6 +70,10 @@ def view(self) -> ViewResource:
def memory(self) -> MemoryResource:
return MemoryResource(self._client)
+ @cached_property
+ def add_async(self) -> AddAsyncResource:
+ return AddAsyncResource(self._client)
+
@cached_property
def with_raw_response(self) -> ContextResourceWithRawResponse:
"""
@@ -201,7 +213,7 @@ def search(
minimum_similarity_threshold: float,
query: str,
similarity_threshold: float,
- metadata: Literal | Omit = omit,
+ metadata: Literal["true", "false"] | Omit = omit,
mode: Literal["fast", "standard"] | Omit = omit,
body_metadata: object | Omit = omit,
scope: Literal["internal", "external"] | Omit = omit,
@@ -295,6 +307,10 @@ def view(self) -> AsyncViewResource:
def memory(self) -> AsyncMemoryResource:
return AsyncMemoryResource(self._client)
+ @cached_property
+ def add_async(self) -> AsyncAddAsyncResource:
+ return AsyncAddAsyncResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncContextResourceWithRawResponse:
"""
@@ -434,7 +450,7 @@ async def search(
minimum_similarity_threshold: float,
query: str,
similarity_threshold: float,
- metadata: Literal | Omit = omit,
+ metadata: Literal["true", "false"] | Omit = omit,
mode: Literal["fast", "standard"] | Omit = omit,
body_metadata: object | Omit = omit,
scope: Literal["internal", "external"] | Omit = omit,
@@ -541,6 +557,10 @@ def view(self) -> ViewResourceWithRawResponse:
def memory(self) -> MemoryResourceWithRawResponse:
return MemoryResourceWithRawResponse(self._context.memory)
+ @cached_property
+ def add_async(self) -> AddAsyncResourceWithRawResponse:
+ return AddAsyncResourceWithRawResponse(self._context.add_async)
+
class AsyncContextResourceWithRawResponse:
def __init__(self, context: AsyncContextResource) -> None:
@@ -568,6 +588,10 @@ def view(self) -> AsyncViewResourceWithRawResponse:
def memory(self) -> AsyncMemoryResourceWithRawResponse:
return AsyncMemoryResourceWithRawResponse(self._context.memory)
+ @cached_property
+ def add_async(self) -> AsyncAddAsyncResourceWithRawResponse:
+ return AsyncAddAsyncResourceWithRawResponse(self._context.add_async)
+
class ContextResourceWithStreamingResponse:
def __init__(self, context: ContextResource) -> None:
@@ -595,6 +619,10 @@ def view(self) -> ViewResourceWithStreamingResponse:
def memory(self) -> MemoryResourceWithStreamingResponse:
return MemoryResourceWithStreamingResponse(self._context.memory)
+ @cached_property
+ def add_async(self) -> AddAsyncResourceWithStreamingResponse:
+ return AddAsyncResourceWithStreamingResponse(self._context.add_async)
+
class AsyncContextResourceWithStreamingResponse:
def __init__(self, context: AsyncContextResource) -> None:
@@ -621,3 +649,7 @@ def view(self) -> AsyncViewResourceWithStreamingResponse:
@cached_property
def memory(self) -> AsyncMemoryResourceWithStreamingResponse:
return AsyncMemoryResourceWithStreamingResponse(self._context.memory)
+
+ @cached_property
+ def add_async(self) -> AsyncAddAsyncResourceWithStreamingResponse:
+ return AsyncAddAsyncResourceWithStreamingResponse(self._context.add_async)
diff --git a/src/alchemyst_ai/resources/v1/context/memory.py b/src/alchemyst_ai/resources/v1/context/memory.py
index 450c03d..d549d70 100644
--- a/src/alchemyst_ai/resources/v1/context/memory.py
+++ b/src/alchemyst_ai/resources/v1/context/memory.py
@@ -48,7 +48,7 @@ def update(
self,
*,
contents: Iterable[memory_update_params.Content],
- memory_id: str,
+ session_id: str,
# 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,
@@ -62,7 +62,7 @@ def update(
Args:
contents: Array of updated content objects
- memory_id: The ID of the memory to update
+ session_id: The ID of the memory to update
extra_headers: Send extra headers
@@ -77,7 +77,7 @@ def update(
body=maybe_transform(
{
"contents": contents,
- "memory_id": memory_id,
+ "session_id": session_id,
},
memory_update_params.MemoryUpdateParams,
),
@@ -147,7 +147,7 @@ def add(
self,
*,
contents: Iterable[memory_add_params.Content],
- memory_id: str,
+ session_id: str,
metadata: memory_add_params.Metadata | 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.
@@ -157,14 +157,16 @@ def add(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MemoryAddResponse:
"""
- This endpoint adds memory context data, fetching chat history if needed.
+ This endpoint adds memory (chat history) as context.
Args:
- contents: Array of content objects with metadata
+ contents: Array of content objects. Each object must contain at least the 'content' field.
+ Additional properties are allowed.
- memory_id: The ID of the memory
+ session_id: The ID of the session
- metadata: Optional metadata with groupName defaulting to ["default"]
+ metadata: Optional metadata for the memory context. Defaults to ["default"] if not
+ provided.
extra_headers: Send extra headers
@@ -179,7 +181,7 @@ def add(
body=maybe_transform(
{
"contents": contents,
- "memory_id": memory_id,
+ "session_id": session_id,
"metadata": metadata,
},
memory_add_params.MemoryAddParams,
@@ -215,7 +217,7 @@ async def update(
self,
*,
contents: Iterable[memory_update_params.Content],
- memory_id: str,
+ session_id: str,
# 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,
@@ -229,7 +231,7 @@ async def update(
Args:
contents: Array of updated content objects
- memory_id: The ID of the memory to update
+ session_id: The ID of the memory to update
extra_headers: Send extra headers
@@ -244,7 +246,7 @@ async def update(
body=await async_maybe_transform(
{
"contents": contents,
- "memory_id": memory_id,
+ "session_id": session_id,
},
memory_update_params.MemoryUpdateParams,
),
@@ -314,7 +316,7 @@ async def add(
self,
*,
contents: Iterable[memory_add_params.Content],
- memory_id: str,
+ session_id: str,
metadata: memory_add_params.Metadata | 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.
@@ -324,14 +326,16 @@ async def add(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MemoryAddResponse:
"""
- This endpoint adds memory context data, fetching chat history if needed.
+ This endpoint adds memory (chat history) as context.
Args:
- contents: Array of content objects with metadata
+ contents: Array of content objects. Each object must contain at least the 'content' field.
+ Additional properties are allowed.
- memory_id: The ID of the memory
+ session_id: The ID of the session
- metadata: Optional metadata with groupName defaulting to ["default"]
+ metadata: Optional metadata for the memory context. Defaults to ["default"] if not
+ provided.
extra_headers: Send extra headers
@@ -346,7 +350,7 @@ async def add(
body=await async_maybe_transform(
{
"contents": contents,
- "memory_id": memory_id,
+ "session_id": session_id,
"metadata": metadata,
},
memory_add_params.MemoryAddParams,
diff --git a/src/alchemyst_ai/types/v1/context/__init__.py b/src/alchemyst_ai/types/v1/context/__init__.py
index 66caec5..971caba 100644
--- a/src/alchemyst_ai/types/v1/context/__init__.py
+++ b/src/alchemyst_ai/types/v1/context/__init__.py
@@ -14,3 +14,6 @@
from .trace_delete_response import TraceDeleteResponse as TraceDeleteResponse
from .memory_update_response import MemoryUpdateResponse as MemoryUpdateResponse
from .view_retrieve_response import ViewRetrieveResponse as ViewRetrieveResponse
+from .add_async_create_params import AddAsyncCreateParams as AddAsyncCreateParams
+from .add_async_cancel_response import AddAsyncCancelResponse as AddAsyncCancelResponse
+from .add_async_create_response import AddAsyncCreateResponse as AddAsyncCreateResponse
diff --git a/src/alchemyst_ai/types/v1/context/add_async/__init__.py b/src/alchemyst_ai/types/v1/context/add_async/__init__.py
new file mode 100644
index 0000000..8fa4546
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async/__init__.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .status_list_params import StatusListParams as StatusListParams
+from .status_list_response import StatusListResponse as StatusListResponse
+from .status_retrieve_response import StatusRetrieveResponse as StatusRetrieveResponse
diff --git a/src/alchemyst_ai/types/v1/context/add_async/status_list_params.py b/src/alchemyst_ai/types/v1/context/add_async/status_list_params.py
new file mode 100644
index 0000000..93fd3e9
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async/status_list_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["StatusListParams"]
+
+
+class StatusListParams(TypedDict, total=False):
+ limit: str
+ """Maximum number of jobs to return"""
+
+ offset: str
+ """Number of jobs to skip before starting to collect the result set"""
+
+ type: Literal["all", "active", "failed", "completed"]
+ """Type of jobs to list"""
diff --git a/src/alchemyst_ai/types/v1/context/add_async/status_list_response.py b/src/alchemyst_ai/types/v1/context/add_async/status_list_response.py
new file mode 100644
index 0000000..1b3c4e3
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async/status_list_response.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = ["StatusListResponse", "Job"]
+
+
+class Job(BaseModel):
+ attempts_made: float = FieldInfo(alias="attemptsMade")
+
+ data: object
+
+ job_id: str = FieldInfo(alias="jobId")
+
+ status: str
+
+ failed_reason: Optional[str] = FieldInfo(alias="failedReason", default=None)
+
+ finished_on: Optional[float] = FieldInfo(alias="finishedOn", default=None)
+
+ processed_on: Optional[float] = FieldInfo(alias="processedOn", default=None)
+
+
+class StatusListResponse(BaseModel):
+ jobs: List[Job]
+
+ success: bool
diff --git a/src/alchemyst_ai/types/v1/context/add_async/status_retrieve_response.py b/src/alchemyst_ai/types/v1/context/add_async/status_retrieve_response.py
new file mode 100644
index 0000000..065ab0d
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async/status_retrieve_response.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = ["StatusRetrieveResponse"]
+
+
+class StatusRetrieveResponse(BaseModel):
+ job_id: str = FieldInfo(alias="jobId")
+
+ status: str
+
+ success: bool
+
+ attempts_made: Optional[float] = FieldInfo(alias="attemptsMade", default=None)
+
+ failed_reason: Optional[str] = FieldInfo(alias="failedReason", default=None)
+
+ finished_on: Optional[float] = FieldInfo(alias="finishedOn", default=None)
+
+ processed_on: Optional[float] = FieldInfo(alias="processedOn", default=None)
+
+ result: Optional[object] = None
+ """Result of the job (if available)"""
diff --git a/src/alchemyst_ai/types/v1/context/add_async_cancel_response.py b/src/alchemyst_ai/types/v1/context/add_async_cancel_response.py
new file mode 100644
index 0000000..33b816a
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async_cancel_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["AddAsyncCancelResponse"]
+
+
+class AddAsyncCancelResponse(BaseModel):
+ job_id: str = FieldInfo(alias="jobId")
+
+ message: str
+
+ status: str
+
+ success: bool
diff --git a/src/alchemyst_ai/types/v1/context/add_async_create_params.py b/src/alchemyst_ai/types/v1/context/add_async_create_params.py
new file mode 100644
index 0000000..12512b9
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async_create_params.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Iterable
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from ...._types import SequenceNotStr
+from ...._utils import PropertyInfo
+
+__all__ = ["AddAsyncCreateParams", "Document", "Metadata"]
+
+
+class AddAsyncCreateParams(TypedDict, total=False):
+ context_type: Required[Literal["resource", "conversation", "instruction"]]
+ """Type of context being added"""
+
+ documents: Required[Iterable[Document]]
+ """Array of documents with content and additional metadata"""
+
+ scope: Required[Literal["internal", "external"]]
+ """Scope of the context"""
+
+ source: Required[str]
+ """The source of the context data"""
+
+ metadata: Metadata
+ """Additional metadata for the context"""
+
+
+class DocumentTyped(TypedDict, total=False):
+ content: str
+ """The content of the document"""
+
+
+Document: TypeAlias = Union[DocumentTyped, Dict[str, str]]
+
+
+class Metadata(TypedDict, total=False):
+ """Additional metadata for the context"""
+
+ file_name: Annotated[str, PropertyInfo(alias="fileName")]
+ """Name of the file"""
+
+ file_size: Annotated[float, PropertyInfo(alias="fileSize")]
+ """Size of the file in bytes"""
+
+ file_type: Annotated[str, PropertyInfo(alias="fileType")]
+ """Type/MIME of the file"""
+
+ group_name: Annotated[SequenceNotStr[str], PropertyInfo(alias="groupName")]
+ """Array of Group Name to which the file belongs to"""
+
+ last_modified: Annotated[str, PropertyInfo(alias="lastModified")]
+ """Last modified timestamp"""
diff --git a/src/alchemyst_ai/types/v1/context/add_async_create_response.py b/src/alchemyst_ai/types/v1/context/add_async_create_response.py
new file mode 100644
index 0000000..484f567
--- /dev/null
+++ b/src/alchemyst_ai/types/v1/context/add_async_create_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["AddAsyncCreateResponse"]
+
+
+class AddAsyncCreateResponse(BaseModel):
+ job_id: str = FieldInfo(alias="jobId")
+
+ queued: bool
diff --git a/src/alchemyst_ai/types/v1/context/memory_add_params.py b/src/alchemyst_ai/types/v1/context/memory_add_params.py
index 6688f40..bdfe560 100644
--- a/src/alchemyst_ai/types/v1/context/memory_add_params.py
+++ b/src/alchemyst_ai/types/v1/context/memory_add_params.py
@@ -13,38 +13,51 @@
class MemoryAddParams(TypedDict, total=False):
contents: Required[Iterable[Content]]
- """Array of content objects with metadata"""
+ """Array of content objects.
- memory_id: Required[Annotated[str, PropertyInfo(alias="memoryId")]]
- """The ID of the memory"""
+ Each object must contain at least the 'content' field. Additional properties are
+ allowed.
+ """
+
+ session_id: Required[Annotated[str, PropertyInfo(alias="sessionId")]]
+ """The ID of the session"""
metadata: Metadata
- """Optional metadata with groupName defaulting to ["default"]"""
+ """Optional metadata for the memory context.
+ Defaults to ["default"] if not provided.
+ """
-class ContentMetadataTyped(TypedDict, total=False):
- message_id: Required[Annotated[str, PropertyInfo(alias="messageId")]]
- """Unique message ID"""
+class ContentMetadata(TypedDict, total=False):
+ """Additional metadata for the message (optional)"""
-ContentMetadata: TypeAlias = Union[ContentMetadataTyped, Dict[str, object]]
+ message_id: Annotated[str, PropertyInfo(alias="messageId")]
+ """Unique message ID"""
class ContentTyped(TypedDict, total=False):
content: Required[str]
"""The content of the memory message"""
- metadata: Required[ContentMetadata]
+ metadata: ContentMetadata
+ """Additional metadata for the message (optional)"""
Content: TypeAlias = Union[ContentTyped, Dict[str, object]]
class MetadataTyped(TypedDict, total=False):
- """Optional metadata with groupName defaulting to ["default"]"""
+ """Optional metadata for the memory context.
+
+ Defaults to ["default"] if not provided.
+ """
group_name: Annotated[SequenceNotStr[str], PropertyInfo(alias="groupName")]
- """Group names for the memory context"""
+ """Optional group names for the memory context.
+
+ Defaults to ["default"] if not provided.
+ """
Metadata: TypeAlias = Union[MetadataTyped, Dict[str, object]]
diff --git a/src/alchemyst_ai/types/v1/context/memory_update_params.py b/src/alchemyst_ai/types/v1/context/memory_update_params.py
index a890494..d786b79 100644
--- a/src/alchemyst_ai/types/v1/context/memory_update_params.py
+++ b/src/alchemyst_ai/types/v1/context/memory_update_params.py
@@ -14,7 +14,7 @@ class MemoryUpdateParams(TypedDict, total=False):
contents: Required[Iterable[Content]]
"""Array of updated content objects"""
- memory_id: Required[Annotated[str, PropertyInfo(alias="memoryId")]]
+ session_id: Required[Annotated[str, PropertyInfo(alias="sessionId")]]
"""The ID of the memory to update"""
diff --git a/src/alchemyst_ai/types/v1/context_search_params.py b/src/alchemyst_ai/types/v1/context_search_params.py
index 789711b..a516136 100644
--- a/src/alchemyst_ai/types/v1/context_search_params.py
+++ b/src/alchemyst_ai/types/v1/context_search_params.py
@@ -17,7 +17,7 @@ class ContextSearchParams(TypedDict, total=False):
similarity_threshold: Required[float]
"""Maximum similarity threshold (must be >= minimum_similarity_threshold)"""
- metadata: Literal
+ metadata: Literal["true", "false"]
"""Controls whether metadata is included in the response:
- metadata=true → metadata will be included in each context item in the
diff --git a/tests/api_resources/v1/context/add_async/__init__.py b/tests/api_resources/v1/context/add_async/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/v1/context/add_async/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/v1/context/add_async/test_status.py b/tests/api_resources/v1/context/add_async/test_status.py
new file mode 100644
index 0000000..189c8e3
--- /dev/null
+++ b/tests/api_resources/v1/context/add_async/test_status.py
@@ -0,0 +1,184 @@
+# 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 tests.utils import assert_matches_type
+from alchemyst_ai import AlchemystAI, AsyncAlchemystAI
+from alchemyst_ai.types.v1.context.add_async import StatusListResponse, StatusRetrieveResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestStatus:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: AlchemystAI) -> None:
+ status = client.v1.context.add_async.status.retrieve(
+ "id",
+ )
+ assert_matches_type(StatusRetrieveResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: AlchemystAI) -> None:
+ response = client.v1.context.add_async.status.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ status = response.parse()
+ assert_matches_type(StatusRetrieveResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: AlchemystAI) -> None:
+ with client.v1.context.add_async.status.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ status = response.parse()
+ assert_matches_type(StatusRetrieveResponse, status, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: AlchemystAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.v1.context.add_async.status.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: AlchemystAI) -> None:
+ status = client.v1.context.add_async.status.list()
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: AlchemystAI) -> None:
+ status = client.v1.context.add_async.status.list(
+ limit="limit",
+ offset="offset",
+ type="all",
+ )
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: AlchemystAI) -> None:
+ response = client.v1.context.add_async.status.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ status = response.parse()
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: AlchemystAI) -> None:
+ with client.v1.context.add_async.status.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ status = response.parse()
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncStatus:
+ 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_retrieve(self, async_client: AsyncAlchemystAI) -> None:
+ status = await async_client.v1.context.add_async.status.retrieve(
+ "id",
+ )
+ assert_matches_type(StatusRetrieveResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncAlchemystAI) -> None:
+ response = await async_client.v1.context.add_async.status.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ status = await response.parse()
+ assert_matches_type(StatusRetrieveResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncAlchemystAI) -> None:
+ async with async_client.v1.context.add_async.status.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ status = await response.parse()
+ assert_matches_type(StatusRetrieveResponse, status, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncAlchemystAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.v1.context.add_async.status.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncAlchemystAI) -> None:
+ status = await async_client.v1.context.add_async.status.list()
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncAlchemystAI) -> None:
+ status = await async_client.v1.context.add_async.status.list(
+ limit="limit",
+ offset="offset",
+ type="all",
+ )
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncAlchemystAI) -> None:
+ response = await async_client.v1.context.add_async.status.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ status = await response.parse()
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncAlchemystAI) -> None:
+ async with async_client.v1.context.add_async.status.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ status = await response.parse()
+ assert_matches_type(StatusListResponse, status, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/v1/context/test_add_async.py b/tests/api_resources/v1/context/test_add_async.py
new file mode 100644
index 0000000..ddf8684
--- /dev/null
+++ b/tests/api_resources/v1/context/test_add_async.py
@@ -0,0 +1,230 @@
+# 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 tests.utils import assert_matches_type
+from alchemyst_ai import AlchemystAI, AsyncAlchemystAI
+from alchemyst_ai.types.v1.context import AddAsyncCancelResponse, AddAsyncCreateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAddAsync:
+ 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: AlchemystAI) -> None:
+ add_async = client.v1.context.add_async.create(
+ context_type="resource",
+ documents=[{}],
+ scope="internal",
+ source="support-inbox",
+ )
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: AlchemystAI) -> None:
+ add_async = client.v1.context.add_async.create(
+ context_type="resource",
+ documents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ scope="internal",
+ source="support-inbox",
+ metadata={
+ "file_name": "support_thread_TCK-1234.txt",
+ "file_size": 2048,
+ "file_type": "text/plain",
+ "group_name": ["support", "pricing"],
+ "last_modified": "2025-01-10T12:34:56.000Z",
+ },
+ )
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: AlchemystAI) -> None:
+ response = client.v1.context.add_async.with_raw_response.create(
+ context_type="resource",
+ documents=[{}],
+ scope="internal",
+ source="support-inbox",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ add_async = response.parse()
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: AlchemystAI) -> None:
+ with client.v1.context.add_async.with_streaming_response.create(
+ context_type="resource",
+ documents=[{}],
+ scope="internal",
+ source="support-inbox",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ add_async = response.parse()
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_cancel(self, client: AlchemystAI) -> None:
+ add_async = client.v1.context.add_async.cancel(
+ "id",
+ )
+ assert_matches_type(AddAsyncCancelResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_cancel(self, client: AlchemystAI) -> None:
+ response = client.v1.context.add_async.with_raw_response.cancel(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ add_async = response.parse()
+ assert_matches_type(AddAsyncCancelResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_cancel(self, client: AlchemystAI) -> None:
+ with client.v1.context.add_async.with_streaming_response.cancel(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ add_async = response.parse()
+ assert_matches_type(AddAsyncCancelResponse, add_async, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_cancel(self, client: AlchemystAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.v1.context.add_async.with_raw_response.cancel(
+ "",
+ )
+
+
+class TestAsyncAddAsync:
+ 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: AsyncAlchemystAI) -> None:
+ add_async = await async_client.v1.context.add_async.create(
+ context_type="resource",
+ documents=[{}],
+ scope="internal",
+ source="support-inbox",
+ )
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncAlchemystAI) -> None:
+ add_async = await async_client.v1.context.add_async.create(
+ context_type="resource",
+ documents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ scope="internal",
+ source="support-inbox",
+ metadata={
+ "file_name": "support_thread_TCK-1234.txt",
+ "file_size": 2048,
+ "file_type": "text/plain",
+ "group_name": ["support", "pricing"],
+ "last_modified": "2025-01-10T12:34:56.000Z",
+ },
+ )
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncAlchemystAI) -> None:
+ response = await async_client.v1.context.add_async.with_raw_response.create(
+ context_type="resource",
+ documents=[{}],
+ scope="internal",
+ source="support-inbox",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ add_async = await response.parse()
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncAlchemystAI) -> None:
+ async with async_client.v1.context.add_async.with_streaming_response.create(
+ context_type="resource",
+ documents=[{}],
+ scope="internal",
+ source="support-inbox",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ add_async = await response.parse()
+ assert_matches_type(AddAsyncCreateResponse, add_async, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncAlchemystAI) -> None:
+ add_async = await async_client.v1.context.add_async.cancel(
+ "id",
+ )
+ assert_matches_type(AddAsyncCancelResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncAlchemystAI) -> None:
+ response = await async_client.v1.context.add_async.with_raw_response.cancel(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ add_async = await response.parse()
+ assert_matches_type(AddAsyncCancelResponse, add_async, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncAlchemystAI) -> None:
+ async with async_client.v1.context.add_async.with_streaming_response.cancel(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ add_async = await response.parse()
+ assert_matches_type(AddAsyncCancelResponse, add_async, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncAlchemystAI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.v1.context.add_async.with_raw_response.cancel(
+ "",
+ )
diff --git a/tests/api_resources/v1/context/test_memory.py b/tests/api_resources/v1/context/test_memory.py
index 68d7261..d1fbafc 100644
--- a/tests/api_resources/v1/context/test_memory.py
+++ b/tests/api_resources/v1/context/test_memory.py
@@ -25,7 +25,7 @@ class TestMemory:
def test_method_update(self, client: AlchemystAI) -> None:
memory = client.v1.context.memory.update(
contents=[{}, {}],
- memory_id="support-thread-TCK-1234",
+ session_id="support-thread-TCK-1234",
)
assert_matches_type(MemoryUpdateResponse, memory, path=["response"])
@@ -34,7 +34,7 @@ def test_method_update(self, client: AlchemystAI) -> None:
def test_raw_response_update(self, client: AlchemystAI) -> None:
response = client.v1.context.memory.with_raw_response.update(
contents=[{}, {}],
- memory_id="support-thread-TCK-1234",
+ session_id="support-thread-TCK-1234",
)
assert response.is_closed is True
@@ -47,7 +47,7 @@ def test_raw_response_update(self, client: AlchemystAI) -> None:
def test_streaming_response_update(self, client: AlchemystAI) -> None:
with client.v1.context.memory.with_streaming_response.update(
contents=[{}, {}],
- memory_id="support-thread-TCK-1234",
+ session_id="support-thread-TCK-1234",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -110,17 +110,8 @@ def test_streaming_response_delete(self, client: AlchemystAI) -> None:
@parametrize
def test_method_add(self, client: AlchemystAI) -> None:
memory = client.v1.context.memory.add(
- contents=[
- {
- "content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
- ],
- memory_id="support-thread-TCK-1234",
+ contents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ session_id="support-thread-TCK-1234",
)
assert_matches_type(MemoryAddResponse, memory, path=["response"])
@@ -131,15 +122,11 @@ def test_method_add_with_all_params(self, client: AlchemystAI) -> None:
contents=[
{
"content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
+ "metadata": {"message_id": "messageId"},
+ }
],
- memory_id="support-thread-TCK-1234",
- metadata={"group_name": ["support", "pricing"]},
+ session_id="support-thread-TCK-1234",
+ metadata={"group_name": ["string"]},
)
assert_matches_type(MemoryAddResponse, memory, path=["response"])
@@ -147,17 +134,8 @@ def test_method_add_with_all_params(self, client: AlchemystAI) -> None:
@parametrize
def test_raw_response_add(self, client: AlchemystAI) -> None:
response = client.v1.context.memory.with_raw_response.add(
- contents=[
- {
- "content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
- ],
- memory_id="support-thread-TCK-1234",
+ contents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ session_id="support-thread-TCK-1234",
)
assert response.is_closed is True
@@ -169,17 +147,8 @@ def test_raw_response_add(self, client: AlchemystAI) -> None:
@parametrize
def test_streaming_response_add(self, client: AlchemystAI) -> None:
with client.v1.context.memory.with_streaming_response.add(
- contents=[
- {
- "content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
- ],
- memory_id="support-thread-TCK-1234",
+ contents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ session_id="support-thread-TCK-1234",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -200,7 +169,7 @@ class TestAsyncMemory:
async def test_method_update(self, async_client: AsyncAlchemystAI) -> None:
memory = await async_client.v1.context.memory.update(
contents=[{}, {}],
- memory_id="support-thread-TCK-1234",
+ session_id="support-thread-TCK-1234",
)
assert_matches_type(MemoryUpdateResponse, memory, path=["response"])
@@ -209,7 +178,7 @@ async def test_method_update(self, async_client: AsyncAlchemystAI) -> None:
async def test_raw_response_update(self, async_client: AsyncAlchemystAI) -> None:
response = await async_client.v1.context.memory.with_raw_response.update(
contents=[{}, {}],
- memory_id="support-thread-TCK-1234",
+ session_id="support-thread-TCK-1234",
)
assert response.is_closed is True
@@ -222,7 +191,7 @@ async def test_raw_response_update(self, async_client: AsyncAlchemystAI) -> None
async def test_streaming_response_update(self, async_client: AsyncAlchemystAI) -> None:
async with async_client.v1.context.memory.with_streaming_response.update(
contents=[{}, {}],
- memory_id="support-thread-TCK-1234",
+ session_id="support-thread-TCK-1234",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -285,17 +254,8 @@ async def test_streaming_response_delete(self, async_client: AsyncAlchemystAI) -
@parametrize
async def test_method_add(self, async_client: AsyncAlchemystAI) -> None:
memory = await async_client.v1.context.memory.add(
- contents=[
- {
- "content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
- ],
- memory_id="support-thread-TCK-1234",
+ contents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ session_id="support-thread-TCK-1234",
)
assert_matches_type(MemoryAddResponse, memory, path=["response"])
@@ -306,15 +266,11 @@ async def test_method_add_with_all_params(self, async_client: AsyncAlchemystAI)
contents=[
{
"content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
+ "metadata": {"message_id": "messageId"},
+ }
],
- memory_id="support-thread-TCK-1234",
- metadata={"group_name": ["support", "pricing"]},
+ session_id="support-thread-TCK-1234",
+ metadata={"group_name": ["string"]},
)
assert_matches_type(MemoryAddResponse, memory, path=["response"])
@@ -322,17 +278,8 @@ async def test_method_add_with_all_params(self, async_client: AsyncAlchemystAI)
@parametrize
async def test_raw_response_add(self, async_client: AsyncAlchemystAI) -> None:
response = await async_client.v1.context.memory.with_raw_response.add(
- contents=[
- {
- "content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
- ],
- memory_id="support-thread-TCK-1234",
+ contents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ session_id="support-thread-TCK-1234",
)
assert response.is_closed is True
@@ -344,17 +291,8 @@ async def test_raw_response_add(self, async_client: AsyncAlchemystAI) -> None:
@parametrize
async def test_streaming_response_add(self, async_client: AsyncAlchemystAI) -> None:
async with async_client.v1.context.memory.with_streaming_response.add(
- contents=[
- {
- "content": "Customer asked about pricing for the Scale plan.",
- "metadata": {"message_id": "msg-1"},
- },
- {
- "content": "Explained the Scale plan pricing and shared the pricing page link.",
- "metadata": {"message_id": "msg-2"},
- },
- ],
- memory_id="support-thread-TCK-1234",
+ contents=[{"content": "Customer asked about pricing for the Scale plan."}],
+ session_id="support-thread-TCK-1234",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/v1/test_context.py b/tests/api_resources/v1/test_context.py
index 3584510..98aad82 100644
--- a/tests/api_resources/v1/test_context.py
+++ b/tests/api_resources/v1/test_context.py
@@ -147,7 +147,7 @@ def test_method_search_with_all_params(self, client: AlchemystAI) -> None:
minimum_similarity_threshold=0.5,
query="What did the customer ask about pricing for the Scale plan?",
similarity_threshold=0.8,
- metadata=None,
+ metadata="true",
mode="fast",
body_metadata={},
scope="internal",
@@ -318,7 +318,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncAlchemystA
minimum_similarity_threshold=0.5,
query="What did the customer ask about pricing for the Scale plan?",
similarity_threshold=0.8,
- metadata=None,
+ metadata="true",
mode="fast",
body_metadata={},
scope="internal",
diff --git a/tests/test_client.py b/tests/test_client.py
index 9601eca..2df3827 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -8,10 +8,11 @@
import json
import asyncio
import inspect
+import dataclasses
import tracemalloc
-from typing import Any, Union, cast
+from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast
from unittest import mock
-from typing_extensions import Literal
+from typing_extensions import Literal, AsyncIterator, override
import httpx
import pytest
@@ -36,6 +37,7 @@
from .utils import update_env
+T = TypeVar("T")
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
api_key = "My API Key"
@@ -50,6 +52,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
return 0.1
+def mirror_request_content(request: httpx.Request) -> httpx.Response:
+ return httpx.Response(200, content=request.content)
+
+
+# note: we can't use the httpx.MockTransport class as it consumes the request
+# body itself, which means we can't test that the body is read lazily
+class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport):
+ def __init__(
+ self,
+ handler: Callable[[httpx.Request], httpx.Response]
+ | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]],
+ ) -> None:
+ self.handler = handler
+
+ @override
+ def handle_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function"
+ assert inspect.isfunction(self.handler), "handler must be a function"
+ return self.handler(request)
+
+ @override
+ async def handle_async_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function"
+ return await self.handler(request)
+
+
+@dataclasses.dataclass
+class Counter:
+ value: int = 0
+
+
+def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
+async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
+
+
def _get_open_connections(client: AlchemystAI | AsyncAlchemystAI) -> int:
transport = client._client._transport
assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
@@ -511,6 +564,70 @@ def test_multipart_repeating_array(self, client: AlchemystAI) -> None:
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload(self, respx_mock: MockRouter, client: AlchemystAI) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ def test_binary_content_upload_with_iterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_sync_iterator([file_content], counter=counter)
+
+ def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=request.read())
+
+ with AlchemystAI(
+ base_url=base_url,
+ api_key=api_key,
+ _strict_response_validation=True,
+ http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: AlchemystAI) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
def test_basic_union_response(self, respx_mock: MockRouter, client: AlchemystAI) -> None:
class Model1(BaseModel):
@@ -1367,6 +1484,72 @@ def test_multipart_repeating_array(self, async_client: AsyncAlchemystAI) -> None
b"",
]
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncAlchemystAI) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = await async_client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ async def test_binary_content_upload_with_asynciterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_async_iterator([file_content], counter=counter)
+
+ async def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=await request.aread())
+
+ async with AsyncAlchemystAI(
+ base_url=base_url,
+ api_key=api_key,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = await client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload_with_body_is_deprecated(
+ self, respx_mock: MockRouter, async_client: AsyncAlchemystAI
+ ) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = await async_client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
@pytest.mark.respx(base_url=base_url)
async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncAlchemystAI) -> None:
class Model1(BaseModel):