diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index eb4e0db..caf1487 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.10.0"
+ ".": "1.11.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index d521f65..9883e5e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 29
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-0a15ddd7e03addf08468ff36ac294458f86a3a990277a71870e4bc293635bef9.yml
-openapi_spec_hash: 8640228f8a86e5dc464dfa2c8205a2a7
-config_hash: 70cdb57c982c578d1961657c07b8b397
+configured_endpoints: 30
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-7ecb40b6650ff002eed02efd1b59c630abe1fb9eb70c9c916c15b115260e5003.yml
+openapi_spec_hash: 2e5c04d1a50afcd0bdbd9737cec227c9
+config_hash: 01e6bd1df0d14c729087edec4e6b6650
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5938671..e0e5254 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## 1.11.0 (2025-12-19)
+
+Full Changelog: [v1.10.0...v1.11.0](https://github.com/ArcadeAI/arcade-py/compare/v1.10.0...v1.11.0)
+
+### Features
+
+* **api:** api update ([f98c6d3](https://github.com/ArcadeAI/arcade-py/commit/f98c6d3ccf44a38525f056bc52011fbdcc3183c1))
+* **api:** api update ([60e16d2](https://github.com/ArcadeAI/arcade-py/commit/60e16d2e59496bcf64c28328e3a351925b82dccd))
+
+
+### Bug Fixes
+
+* compat with Python 3.14 ([4453f62](https://github.com/ArcadeAI/arcade-py/commit/4453f62ffe78f5bf6ec84c89566a2581ef381cca))
+* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([d4527cf](https://github.com/ArcadeAI/arcade-py/commit/d4527cf2a0ab18f31c0b75d6b452a09f47bd15f5))
+* ensure streams are always closed ([e4be19e](https://github.com/ArcadeAI/arcade-py/commit/e4be19ee70952d16361d55acfc082875bd926c01))
+* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([6834686](https://github.com/ArcadeAI/arcade-py/commit/6834686ece469c756682bb559f8bca5b5b316296))
+* use async_to_httpx_files in patch method ([855d52c](https://github.com/ArcadeAI/arcade-py/commit/855d52c4817ecb2421b8467a0190f1222b1306de))
+
+
+### Chores
+
+* add missing docstrings ([d834791](https://github.com/ArcadeAI/arcade-py/commit/d8347917cfbe05f70e64d6c01ede460e15e20896))
+* add Python 3.14 classifier and testing ([8c6d5c5](https://github.com/ArcadeAI/arcade-py/commit/8c6d5c5ace8884839590c11a0b72f9ea70731e0e))
+* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([0c9e340](https://github.com/ArcadeAI/arcade-py/commit/0c9e3402e6714d2c1e4273ff5bd0e2dc00cce7b2))
+* **docs:** use environment variables for authentication in code snippets ([de0cde1](https://github.com/ArcadeAI/arcade-py/commit/de0cde1a836d5b1992d8cdc284c3decbfa8eb20b))
+* **internal:** add `--fix` argument to lint script ([461ab0b](https://github.com/ArcadeAI/arcade-py/commit/461ab0bfb6285d29b773511c0350efcd1a011b7e))
+* **internal:** add missing files argument to base client ([eb32a75](https://github.com/ArcadeAI/arcade-py/commit/eb32a7551debd52fdfc15f3e7948d35e48f20875))
+* **package:** drop Python 3.8 support ([d68084f](https://github.com/ArcadeAI/arcade-py/commit/d68084f663f4cf682457a195918d95cce6584772))
+* speedup initial import ([12abf0c](https://github.com/ArcadeAI/arcade-py/commit/12abf0c7c4493c0ff4e27db53679e65dde59eab9))
+* update lockfile ([d0dae69](https://github.com/ArcadeAI/arcade-py/commit/d0dae695284c4ee2873529f0e24034dc409e99e7))
+
## 1.10.0 (2025-11-06)
Full Changelog: [v1.9.0...v1.10.0](https://github.com/ArcadeAI/arcade-py/compare/v1.9.0...v1.10.0)
diff --git a/README.md b/README.md
index 0a1de1e..80f0d66 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[)](https://pypi.org/project/arcadepy/)
-The Arcade Python library provides convenient access to the Arcade REST API from any Python 3.8+
+The Arcade Python library provides convenient access to the Arcade REST API from any Python 3.9+
application. The library includes type definitions for all request params and response fields,
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
@@ -87,6 +87,7 @@ pip install arcadepy[aiohttp]
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
```python
+import os
import asyncio
from arcadepy import DefaultAioHttpClient
from arcadepy import AsyncArcade
@@ -94,7 +95,7 @@ from arcadepy import AsyncArcade
async def main() -> None:
async with AsyncArcade(
- api_key="My API Key",
+ api_key=os.environ.get("ARCADE_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
execute_tool_response = await client.tools.execute(
@@ -416,7 +417,7 @@ print(arcadepy.__version__)
## Requirements
-Python 3.8 or higher.
+Python 3.9 or higher.
## Contributing
diff --git a/api.md b/api.md
index 55a932d..61b6e13 100644
--- a/api.md
+++ b/api.md
@@ -50,6 +50,7 @@ from arcadepy.types.admin import SecretResponse, SecretListResponse
Methods:
+- client.admin.secrets.create(secret_key, \*\*params) -> SecretResponse
- client.admin.secrets.list() -> SecretListResponse
- client.admin.secrets.delete(secret_id) -> None
diff --git a/pyproject.toml b/pyproject.toml
index c588719..2eed60c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,30 +1,32 @@
[project]
name = "arcadepy"
-version = "1.10.0"
+version = "1.11.0"
description = "The official Python library for the Arcade API"
dynamic = ["readme"]
license = "MIT"
authors = [
{ name = "Arcade", email = "dev@arcade.dev" },
]
+
dependencies = [
- "httpx>=0.23.0, <1",
- "pydantic>=1.9.0, <3",
- "typing-extensions>=4.10, <5",
- "anyio>=3.5.0, <5",
- "distro>=1.7.0, <2",
- "sniffio",
+ "httpx>=0.23.0, <1",
+ "pydantic>=1.9.0, <3",
+ "typing-extensions>=4.10, <5",
+ "anyio>=3.5.0, <5",
+ "distro>=1.7.0, <2",
+ "sniffio",
]
-requires-python = ">= 3.8"
+
+requires-python = ">= 3.9"
classifiers = [
"Typing :: Typed",
"Intended Audience :: Developers",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Operating System :: MacOS",
@@ -46,7 +48,7 @@ managed = true
# version pins are in requirements-dev.lock
dev-dependencies = [
"pyright==1.1.399",
- "mypy",
+ "mypy==1.17",
"respx",
"pytest",
"pytest-asyncio",
@@ -141,7 +143,7 @@ filterwarnings = [
# there are a couple of flags that are still disabled by
# default in strict mode as they are experimental and niche.
typeCheckingMode = "strict"
-pythonVersion = "3.8"
+pythonVersion = "3.9"
exclude = [
"_dev",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 3c2bd65..cb9974b 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -12,40 +12,45 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.12.8
+aiohttp==3.13.2
# via arcadepy
# via httpx-aiohttp
-aiosignal==1.3.2
+aiosignal==1.4.0
# via aiohttp
-annotated-types==0.6.0
+annotated-types==0.7.0
# via pydantic
-anyio==4.4.0
+anyio==4.12.0
# via arcadepy
# via httpx
-argcomplete==3.1.2
+argcomplete==3.6.3
# via nox
async-timeout==5.0.1
# via aiohttp
-attrs==25.3.0
+attrs==25.4.0
# via aiohttp
-certifi==2023.7.22
+ # via nox
+backports-asyncio-runner==1.2.0
+ # via pytest-asyncio
+certifi==2025.11.12
# via httpcore
# via httpx
-colorlog==6.7.0
+colorlog==6.10.1
+ # via nox
+dependency-groups==1.3.1
# via nox
-dirty-equals==0.6.0
-distlib==0.3.7
+dirty-equals==0.11
+distlib==0.4.0
# via virtualenv
-distro==1.8.0
+distro==1.9.0
# via arcadepy
-exceptiongroup==1.2.2
+exceptiongroup==1.3.1
# via anyio
# via pytest
-execnet==2.1.1
+execnet==2.1.2
# via pytest-xdist
-filelock==3.12.4
+filelock==3.19.1
# via virtualenv
-frozenlist==1.6.2
+frozenlist==1.8.0
# via aiohttp
# via aiosignal
h11==0.16.0
@@ -58,80 +63,87 @@ httpx==0.28.1
# via respx
httpx-aiohttp==0.1.9
# via arcadepy
-idna==3.4
+humanize==4.13.0
+ # via nox
+idna==3.11
# via anyio
# via httpx
# via yarl
-importlib-metadata==7.0.0
-iniconfig==2.0.0
+importlib-metadata==8.7.0
+iniconfig==2.1.0
# via pytest
markdown-it-py==3.0.0
# via rich
mdurl==0.1.2
# via markdown-it-py
-multidict==6.4.4
+multidict==6.7.0
# via aiohttp
# via yarl
-mypy==1.14.1
-mypy-extensions==1.0.0
+mypy==1.17.0
+mypy-extensions==1.1.0
# via mypy
-nodeenv==1.8.0
+nodeenv==1.9.1
# via pyright
-nox==2023.4.22
-packaging==23.2
+nox==2025.11.12
+packaging==25.0
+ # via dependency-groups
# via nox
# via pytest
-platformdirs==3.11.0
+pathspec==0.12.1
+ # via mypy
+platformdirs==4.4.0
# via virtualenv
-pluggy==1.5.0
+pluggy==1.6.0
# via pytest
-propcache==0.3.1
+propcache==0.4.1
# via aiohttp
# via yarl
-pydantic==2.11.9
+pydantic==2.12.5
# via arcadepy
-pydantic-core==2.33.2
+pydantic-core==2.41.5
# via pydantic
-pygments==2.18.0
+pygments==2.19.2
+ # via pytest
# via rich
pyright==1.1.399
-pytest==8.3.3
+pytest==8.4.2
# via pytest-asyncio
# via pytest-xdist
-pytest-asyncio==0.24.0
-pytest-xdist==3.7.0
-python-dateutil==2.8.2
+pytest-asyncio==1.2.0
+pytest-xdist==3.8.0
+python-dateutil==2.9.0.post0
# via time-machine
-pytz==2023.3.post1
- # via dirty-equals
respx==0.22.0
-rich==13.7.1
-ruff==0.9.4
-setuptools==68.2.2
- # via nodeenv
-six==1.16.0
+rich==14.2.0
+ruff==0.14.7
+six==1.17.0
# via python-dateutil
-sniffio==1.3.0
- # via anyio
+sniffio==1.3.1
# via arcadepy
-time-machine==2.9.0
-tomli==2.0.2
+time-machine==2.19.0
+tomli==2.3.0
+ # via dependency-groups
# via mypy
+ # via nox
# via pytest
-typing-extensions==4.12.2
+typing-extensions==4.15.0
+ # via aiosignal
# via anyio
# via arcadepy
+ # via exceptiongroup
# via multidict
# via mypy
# via pydantic
# via pydantic-core
# via pyright
+ # via pytest-asyncio
# via typing-inspection
-typing-inspection==0.4.1
+ # via virtualenv
+typing-inspection==0.4.2
# via pydantic
-virtualenv==20.24.5
+virtualenv==20.35.4
# via nox
-yarl==1.20.0
+yarl==1.22.0
# via aiohttp
-zipp==3.17.0
+zipp==3.23.0
# via importlib-metadata
diff --git a/requirements.lock b/requirements.lock
index b5a9719..9c47af4 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -12,28 +12,28 @@
-e file:.
aiohappyeyeballs==2.6.1
# via aiohttp
-aiohttp==3.12.8
+aiohttp==3.13.2
# via arcadepy
# via httpx-aiohttp
-aiosignal==1.3.2
+aiosignal==1.4.0
# via aiohttp
-annotated-types==0.6.0
+annotated-types==0.7.0
# via pydantic
-anyio==4.4.0
+anyio==4.12.0
# via arcadepy
# via httpx
async-timeout==5.0.1
# via aiohttp
-attrs==25.3.0
+attrs==25.4.0
# via aiohttp
-certifi==2023.7.22
+certifi==2025.11.12
# via httpcore
# via httpx
-distro==1.8.0
+distro==1.9.0
# via arcadepy
-exceptiongroup==1.2.2
+exceptiongroup==1.3.1
# via anyio
-frozenlist==1.6.2
+frozenlist==1.8.0
# via aiohttp
# via aiosignal
h11==0.16.0
@@ -45,31 +45,32 @@ httpx==0.28.1
# via httpx-aiohttp
httpx-aiohttp==0.1.9
# via arcadepy
-idna==3.4
+idna==3.11
# via anyio
# via httpx
# via yarl
-multidict==6.4.4
+multidict==6.7.0
# via aiohttp
# via yarl
-propcache==0.3.1
+propcache==0.4.1
# via aiohttp
# via yarl
-pydantic==2.11.9
+pydantic==2.12.5
# via arcadepy
-pydantic-core==2.33.2
+pydantic-core==2.41.5
# via pydantic
-sniffio==1.3.0
- # via anyio
+sniffio==1.3.1
# via arcadepy
-typing-extensions==4.12.2
+typing-extensions==4.15.0
+ # via aiosignal
# via anyio
# via arcadepy
+ # via exceptiongroup
# via multidict
# via pydantic
# via pydantic-core
# via typing-inspection
-typing-inspection==0.4.1
+typing-inspection==0.4.2
# via pydantic
-yarl==1.20.0
+yarl==1.22.0
# via aiohttp
diff --git a/scripts/lint b/scripts/lint
index 17c73a3..dc8016b 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -4,8 +4,13 @@ set -e
cd "$(dirname "$0")/.."
-echo "==> Running lints"
-rye run lint
+if [ "$1" = "--fix" ]; then
+ echo "==> Running lints with --fix"
+ rye run fix:ruff
+else
+ echo "==> Running lints"
+ rye run lint
+fi
echo "==> Making sure it imports"
rye run python -c 'import arcadepy'
diff --git a/src/arcadepy/_base_client.py b/src/arcadepy/_base_client.py
index 57cf33c..02030fd 100644
--- a/src/arcadepy/_base_client.py
+++ b/src/arcadepy/_base_client.py
@@ -1247,9 +1247,12 @@ def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
+ )
return self.request(cast_to, opts)
def put(
@@ -1767,9 +1770,12 @@ async def patch(
*,
cast_to: Type[ResponseT],
body: Body | None = None,
+ files: RequestFiles | None = None,
options: RequestOptions = {},
) -> ResponseT:
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options
+ )
return await self.request(cast_to, opts)
async def put(
diff --git a/src/arcadepy/_client.py b/src/arcadepy/_client.py
index 7ad9056..a261011 100644
--- a/src/arcadepy/_client.py
+++ b/src/arcadepy/_client.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import os
-from typing import Any, Mapping
+from typing import TYPE_CHECKING, Any, Mapping
from typing_extensions import Self, override
import httpx
@@ -20,8 +20,8 @@
not_given,
)
from ._utils import is_given, get_async_library
+from ._compat import cached_property
from ._version import __version__
-from .resources import auth, health, workers
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import ArcadeError, APIStatusError
from ._base_client import (
@@ -29,23 +29,20 @@
SyncAPIClient,
AsyncAPIClient,
)
-from .resources.chat import chat
-from .resources.admin import admin
-from .resources.tools import tools
+
+if TYPE_CHECKING:
+ from .resources import auth, chat, admin, tools, health, workers
+ from .resources.auth import AuthResource, AsyncAuthResource
+ from .resources.health import HealthResource, AsyncHealthResource
+ from .resources.workers import WorkersResource, AsyncWorkersResource
+ from .resources.chat.chat import ChatResource, AsyncChatResource
+ from .resources.admin.admin import AdminResource, AsyncAdminResource
+ from .resources.tools.tools import ToolsResource, AsyncToolsResource
__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Arcade", "AsyncArcade", "Client", "AsyncClient"]
class Arcade(SyncAPIClient):
- admin: admin.AdminResource
- auth: auth.AuthResource
- health: health.HealthResource
- chat: chat.ChatResource
- tools: tools.ToolsResource
- workers: workers.WorkersResource
- with_raw_response: ArcadeWithRawResponse
- with_streaming_response: ArcadeWithStreamedResponse
-
# client options
api_key: str
@@ -100,14 +97,49 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
- self.admin = admin.AdminResource(self)
- self.auth = auth.AuthResource(self)
- self.health = health.HealthResource(self)
- self.chat = chat.ChatResource(self)
- self.tools = tools.ToolsResource(self)
- self.workers = workers.WorkersResource(self)
- self.with_raw_response = ArcadeWithRawResponse(self)
- self.with_streaming_response = ArcadeWithStreamedResponse(self)
+ @cached_property
+ def admin(self) -> AdminResource:
+ from .resources.admin import AdminResource
+
+ return AdminResource(self)
+
+ @cached_property
+ def auth(self) -> AuthResource:
+ from .resources.auth import AuthResource
+
+ return AuthResource(self)
+
+ @cached_property
+ def health(self) -> HealthResource:
+ from .resources.health import HealthResource
+
+ return HealthResource(self)
+
+ @cached_property
+ def chat(self) -> ChatResource:
+ from .resources.chat import ChatResource
+
+ return ChatResource(self)
+
+ @cached_property
+ def tools(self) -> ToolsResource:
+ from .resources.tools import ToolsResource
+
+ return ToolsResource(self)
+
+ @cached_property
+ def workers(self) -> WorkersResource:
+ from .resources.workers import WorkersResource
+
+ return WorkersResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> ArcadeWithRawResponse:
+ return ArcadeWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ArcadeWithStreamedResponse:
+ return ArcadeWithStreamedResponse(self)
@property
@override
@@ -215,15 +247,6 @@ def _make_status_error(
class AsyncArcade(AsyncAPIClient):
- admin: admin.AsyncAdminResource
- auth: auth.AsyncAuthResource
- health: health.AsyncHealthResource
- chat: chat.AsyncChatResource
- tools: tools.AsyncToolsResource
- workers: workers.AsyncWorkersResource
- with_raw_response: AsyncArcadeWithRawResponse
- with_streaming_response: AsyncArcadeWithStreamedResponse
-
# client options
api_key: str
@@ -278,14 +301,49 @@ def __init__(
_strict_response_validation=_strict_response_validation,
)
- self.admin = admin.AsyncAdminResource(self)
- self.auth = auth.AsyncAuthResource(self)
- self.health = health.AsyncHealthResource(self)
- self.chat = chat.AsyncChatResource(self)
- self.tools = tools.AsyncToolsResource(self)
- self.workers = workers.AsyncWorkersResource(self)
- self.with_raw_response = AsyncArcadeWithRawResponse(self)
- self.with_streaming_response = AsyncArcadeWithStreamedResponse(self)
+ @cached_property
+ def admin(self) -> AsyncAdminResource:
+ from .resources.admin import AsyncAdminResource
+
+ return AsyncAdminResource(self)
+
+ @cached_property
+ def auth(self) -> AsyncAuthResource:
+ from .resources.auth import AsyncAuthResource
+
+ return AsyncAuthResource(self)
+
+ @cached_property
+ def health(self) -> AsyncHealthResource:
+ from .resources.health import AsyncHealthResource
+
+ return AsyncHealthResource(self)
+
+ @cached_property
+ def chat(self) -> AsyncChatResource:
+ from .resources.chat import AsyncChatResource
+
+ return AsyncChatResource(self)
+
+ @cached_property
+ def tools(self) -> AsyncToolsResource:
+ from .resources.tools import AsyncToolsResource
+
+ return AsyncToolsResource(self)
+
+ @cached_property
+ def workers(self) -> AsyncWorkersResource:
+ from .resources.workers import AsyncWorkersResource
+
+ return AsyncWorkersResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncArcadeWithRawResponse:
+ return AsyncArcadeWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncArcadeWithStreamedResponse:
+ return AsyncArcadeWithStreamedResponse(self)
@property
@override
@@ -393,43 +451,175 @@ def _make_status_error(
class ArcadeWithRawResponse:
+ _client: Arcade
+
def __init__(self, client: Arcade) -> None:
- self.admin = admin.AdminResourceWithRawResponse(client.admin)
- self.auth = auth.AuthResourceWithRawResponse(client.auth)
- self.health = health.HealthResourceWithRawResponse(client.health)
- self.chat = chat.ChatResourceWithRawResponse(client.chat)
- self.tools = tools.ToolsResourceWithRawResponse(client.tools)
- self.workers = workers.WorkersResourceWithRawResponse(client.workers)
+ self._client = client
+
+ @cached_property
+ def admin(self) -> admin.AdminResourceWithRawResponse:
+ from .resources.admin import AdminResourceWithRawResponse
+
+ return AdminResourceWithRawResponse(self._client.admin)
+
+ @cached_property
+ def auth(self) -> auth.AuthResourceWithRawResponse:
+ from .resources.auth import AuthResourceWithRawResponse
+
+ return AuthResourceWithRawResponse(self._client.auth)
+
+ @cached_property
+ def health(self) -> health.HealthResourceWithRawResponse:
+ from .resources.health import HealthResourceWithRawResponse
+
+ return HealthResourceWithRawResponse(self._client.health)
+
+ @cached_property
+ def chat(self) -> chat.ChatResourceWithRawResponse:
+ from .resources.chat import ChatResourceWithRawResponse
+
+ return ChatResourceWithRawResponse(self._client.chat)
+
+ @cached_property
+ def tools(self) -> tools.ToolsResourceWithRawResponse:
+ from .resources.tools import ToolsResourceWithRawResponse
+
+ return ToolsResourceWithRawResponse(self._client.tools)
+
+ @cached_property
+ def workers(self) -> workers.WorkersResourceWithRawResponse:
+ from .resources.workers import WorkersResourceWithRawResponse
+
+ return WorkersResourceWithRawResponse(self._client.workers)
class AsyncArcadeWithRawResponse:
+ _client: AsyncArcade
+
def __init__(self, client: AsyncArcade) -> None:
- self.admin = admin.AsyncAdminResourceWithRawResponse(client.admin)
- self.auth = auth.AsyncAuthResourceWithRawResponse(client.auth)
- self.health = health.AsyncHealthResourceWithRawResponse(client.health)
- self.chat = chat.AsyncChatResourceWithRawResponse(client.chat)
- self.tools = tools.AsyncToolsResourceWithRawResponse(client.tools)
- self.workers = workers.AsyncWorkersResourceWithRawResponse(client.workers)
+ self._client = client
+
+ @cached_property
+ def admin(self) -> admin.AsyncAdminResourceWithRawResponse:
+ from .resources.admin import AsyncAdminResourceWithRawResponse
+
+ return AsyncAdminResourceWithRawResponse(self._client.admin)
+
+ @cached_property
+ def auth(self) -> auth.AsyncAuthResourceWithRawResponse:
+ from .resources.auth import AsyncAuthResourceWithRawResponse
+
+ return AsyncAuthResourceWithRawResponse(self._client.auth)
+
+ @cached_property
+ def health(self) -> health.AsyncHealthResourceWithRawResponse:
+ from .resources.health import AsyncHealthResourceWithRawResponse
+
+ return AsyncHealthResourceWithRawResponse(self._client.health)
+
+ @cached_property
+ def chat(self) -> chat.AsyncChatResourceWithRawResponse:
+ from .resources.chat import AsyncChatResourceWithRawResponse
+
+ return AsyncChatResourceWithRawResponse(self._client.chat)
+
+ @cached_property
+ def tools(self) -> tools.AsyncToolsResourceWithRawResponse:
+ from .resources.tools import AsyncToolsResourceWithRawResponse
+
+ return AsyncToolsResourceWithRawResponse(self._client.tools)
+
+ @cached_property
+ def workers(self) -> workers.AsyncWorkersResourceWithRawResponse:
+ from .resources.workers import AsyncWorkersResourceWithRawResponse
+
+ return AsyncWorkersResourceWithRawResponse(self._client.workers)
class ArcadeWithStreamedResponse:
+ _client: Arcade
+
def __init__(self, client: Arcade) -> None:
- self.admin = admin.AdminResourceWithStreamingResponse(client.admin)
- self.auth = auth.AuthResourceWithStreamingResponse(client.auth)
- self.health = health.HealthResourceWithStreamingResponse(client.health)
- self.chat = chat.ChatResourceWithStreamingResponse(client.chat)
- self.tools = tools.ToolsResourceWithStreamingResponse(client.tools)
- self.workers = workers.WorkersResourceWithStreamingResponse(client.workers)
+ self._client = client
+
+ @cached_property
+ def admin(self) -> admin.AdminResourceWithStreamingResponse:
+ from .resources.admin import AdminResourceWithStreamingResponse
+
+ return AdminResourceWithStreamingResponse(self._client.admin)
+
+ @cached_property
+ def auth(self) -> auth.AuthResourceWithStreamingResponse:
+ from .resources.auth import AuthResourceWithStreamingResponse
+
+ return AuthResourceWithStreamingResponse(self._client.auth)
+
+ @cached_property
+ def health(self) -> health.HealthResourceWithStreamingResponse:
+ from .resources.health import HealthResourceWithStreamingResponse
+
+ return HealthResourceWithStreamingResponse(self._client.health)
+
+ @cached_property
+ def chat(self) -> chat.ChatResourceWithStreamingResponse:
+ from .resources.chat import ChatResourceWithStreamingResponse
+
+ return ChatResourceWithStreamingResponse(self._client.chat)
+
+ @cached_property
+ def tools(self) -> tools.ToolsResourceWithStreamingResponse:
+ from .resources.tools import ToolsResourceWithStreamingResponse
+
+ return ToolsResourceWithStreamingResponse(self._client.tools)
+
+ @cached_property
+ def workers(self) -> workers.WorkersResourceWithStreamingResponse:
+ from .resources.workers import WorkersResourceWithStreamingResponse
+
+ return WorkersResourceWithStreamingResponse(self._client.workers)
class AsyncArcadeWithStreamedResponse:
+ _client: AsyncArcade
+
def __init__(self, client: AsyncArcade) -> None:
- self.admin = admin.AsyncAdminResourceWithStreamingResponse(client.admin)
- self.auth = auth.AsyncAuthResourceWithStreamingResponse(client.auth)
- self.health = health.AsyncHealthResourceWithStreamingResponse(client.health)
- self.chat = chat.AsyncChatResourceWithStreamingResponse(client.chat)
- self.tools = tools.AsyncToolsResourceWithStreamingResponse(client.tools)
- self.workers = workers.AsyncWorkersResourceWithStreamingResponse(client.workers)
+ self._client = client
+
+ @cached_property
+ def admin(self) -> admin.AsyncAdminResourceWithStreamingResponse:
+ from .resources.admin import AsyncAdminResourceWithStreamingResponse
+
+ return AsyncAdminResourceWithStreamingResponse(self._client.admin)
+
+ @cached_property
+ def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse:
+ from .resources.auth import AsyncAuthResourceWithStreamingResponse
+
+ return AsyncAuthResourceWithStreamingResponse(self._client.auth)
+
+ @cached_property
+ def health(self) -> health.AsyncHealthResourceWithStreamingResponse:
+ from .resources.health import AsyncHealthResourceWithStreamingResponse
+
+ return AsyncHealthResourceWithStreamingResponse(self._client.health)
+
+ @cached_property
+ def chat(self) -> chat.AsyncChatResourceWithStreamingResponse:
+ from .resources.chat import AsyncChatResourceWithStreamingResponse
+
+ return AsyncChatResourceWithStreamingResponse(self._client.chat)
+
+ @cached_property
+ def tools(self) -> tools.AsyncToolsResourceWithStreamingResponse:
+ from .resources.tools import AsyncToolsResourceWithStreamingResponse
+
+ return AsyncToolsResourceWithStreamingResponse(self._client.tools)
+
+ @cached_property
+ def workers(self) -> workers.AsyncWorkersResourceWithStreamingResponse:
+ from .resources.workers import AsyncWorkersResourceWithStreamingResponse
+
+ return AsyncWorkersResourceWithStreamingResponse(self._client.workers)
Client = Arcade
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index 6a3cd1d..ca9500b 100644
--- a/src/arcadepy/_models.py
+++ b/src/arcadepy/_models.py
@@ -2,6 +2,7 @@
import os
import inspect
+import weakref
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from datetime import date, datetime
from typing_extensions import (
@@ -256,15 +257,16 @@ def model_dump(
mode: Literal["json", "python"] | str = "python",
include: IncEx | None = None,
exclude: IncEx | None = None,
+ context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
+ exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
- context: dict[str, Any] | None = None,
- serialize_as_any: bool = False,
fallback: Callable[[Any], Any] | None = None,
+ serialize_as_any: bool = False,
) -> dict[str, Any]:
"""Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump
@@ -272,16 +274,24 @@ def model_dump(
Args:
mode: The mode in which `to_python` should run.
- If mode is 'json', the dictionary will only contain JSON serializable types.
- If mode is 'python', the dictionary may contain any Python objects.
- include: A list of fields to include in the output.
- exclude: A list of fields to exclude from the output.
+ If mode is 'json', the output will only contain JSON serializable types.
+ If mode is 'python', the output may contain non-JSON-serializable Python objects.
+ include: A set of fields to include in the output.
+ exclude: A set of fields to exclude from the output.
+ context: Additional context to pass to the serializer.
by_alias: Whether to use the field's alias in the dictionary key if defined.
- exclude_unset: Whether to exclude fields that are unset or None from the output.
- exclude_defaults: Whether to exclude fields that are set to their default value from the output.
- exclude_none: Whether to exclude fields that have a value of `None` from the output.
- round_trip: Whether to enable serialization and deserialization round-trip support.
- warnings: Whether to log warnings when invalid fields are encountered.
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
+ exclude_defaults: Whether to exclude fields that are set to their default value.
+ exclude_none: Whether to exclude fields that have a value of `None`.
+ exclude_computed_fields: Whether to exclude computed fields.
+ While this can be useful for round-tripping, it is usually recommended to use the dedicated
+ `round_trip` parameter instead.
+ round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
+ warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
+ "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
+ fallback: A function to call when an unknown value is encountered. If not provided,
+ a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
+ serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
Returns:
A dictionary representation of the model.
@@ -298,6 +308,8 @@ def model_dump(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
+ if exclude_computed_fields != False:
+ raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
dumped = super().dict( # pyright: ignore[reportDeprecated]
include=include,
exclude=exclude,
@@ -314,15 +326,17 @@ def model_dump_json(
self,
*,
indent: int | None = None,
+ ensure_ascii: bool = False,
include: IncEx | None = None,
exclude: IncEx | None = None,
+ context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
+ exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
- context: dict[str, Any] | None = None,
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
) -> str:
@@ -354,6 +368,10 @@ def model_dump_json(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
+ if ensure_ascii != False:
+ raise ValueError("ensure_ascii is only supported in Pydantic v2")
+ if exclude_computed_fields != False:
+ raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
return super().json( # type: ignore[reportDeprecated]
indent=indent,
include=include,
@@ -573,6 +591,9 @@ class CachedDiscriminatorType(Protocol):
__discriminator__: DiscriminatorDetails
+DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()
+
+
class DiscriminatorDetails:
field_name: str
"""The name of the discriminator field in the variant class, e.g.
@@ -615,8 +636,9 @@ def __init__(
def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
- if isinstance(union, CachedDiscriminatorType):
- return union.__discriminator__
+ cached = DISCRIMINATOR_CACHE.get(union)
+ if cached is not None:
+ return cached
discriminator_field_name: str | None = None
@@ -669,7 +691,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
discriminator_field=discriminator_field_name,
discriminator_alias=discriminator_alias,
)
- cast(CachedDiscriminatorType, union).__discriminator__ = details
+ DISCRIMINATOR_CACHE.setdefault(union, details)
return details
diff --git a/src/arcadepy/_streaming.py b/src/arcadepy/_streaming.py
index 6bf52e7..d539a62 100644
--- a/src/arcadepy/_streaming.py
+++ b/src/arcadepy/_streaming.py
@@ -54,11 +54,12 @@ def __stream__(self) -> Iterator[_T]:
process_data = self._client._process_response_data
iterator = self._iter_events()
- for sse in iterator:
- yield process_data(data=sse.json(), cast_to=cast_to, response=response)
-
- # As we might not fully consume the response stream, we need to close it explicitly
- response.close()
+ try:
+ for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ response.close()
def __enter__(self) -> Self:
return self
@@ -117,11 +118,12 @@ async def __stream__(self) -> AsyncIterator[_T]:
process_data = self._client._process_response_data
iterator = self._iter_events()
- async for sse in iterator:
- yield process_data(data=sse.json(), cast_to=cast_to, response=response)
-
- # As we might not fully consume the response stream, we need to close it explicitly
- await response.aclose()
+ try:
+ async for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ await response.aclose()
async def __aenter__(self) -> Self:
return self
diff --git a/src/arcadepy/_types.py b/src/arcadepy/_types.py
index 39e9386..4e54c6d 100644
--- a/src/arcadepy/_types.py
+++ b/src/arcadepy/_types.py
@@ -243,6 +243,9 @@ class HttpxSendArgs(TypedDict, total=False):
if TYPE_CHECKING:
# This works because str.__contains__ does not accept object (either in typeshed or at runtime)
# https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
+ #
+ # Note: index() and count() methods are intentionally omitted to allow pyright to properly
+ # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
class SequenceNotStr(Protocol[_T_co]):
@overload
def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
@@ -251,8 +254,6 @@ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
def __contains__(self, value: object, /) -> bool: ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[_T_co]: ...
- def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ...
- def count(self, value: Any, /) -> int: ...
def __reversed__(self) -> Iterator[_T_co]: ...
else:
# just point this to a normal `Sequence` at runtime to avoid having to special case
diff --git a/src/arcadepy/_utils/_sync.py b/src/arcadepy/_utils/_sync.py
index ad7ec71..f6027c1 100644
--- a/src/arcadepy/_utils/_sync.py
+++ b/src/arcadepy/_utils/_sync.py
@@ -1,10 +1,8 @@
from __future__ import annotations
-import sys
import asyncio
import functools
-import contextvars
-from typing import Any, TypeVar, Callable, Awaitable
+from typing import TypeVar, Callable, Awaitable
from typing_extensions import ParamSpec
import anyio
@@ -15,34 +13,11 @@
T_ParamSpec = ParamSpec("T_ParamSpec")
-if sys.version_info >= (3, 9):
- _asyncio_to_thread = asyncio.to_thread
-else:
- # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
- # for Python 3.8 support
- async def _asyncio_to_thread(
- func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
- ) -> Any:
- """Asynchronously run function *func* in a separate thread.
-
- Any *args and **kwargs supplied for this function are directly passed
- to *func*. Also, the current :class:`contextvars.Context` is propagated,
- allowing context variables from the main thread to be accessed in the
- separate thread.
-
- Returns a coroutine that can be awaited to get the eventual result of *func*.
- """
- loop = asyncio.events.get_running_loop()
- ctx = contextvars.copy_context()
- func_call = functools.partial(ctx.run, func, *args, **kwargs)
- return await loop.run_in_executor(None, func_call)
-
-
async def to_thread(
func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> T_Retval:
if sniffio.current_async_library() == "asyncio":
- return await _asyncio_to_thread(func, *args, **kwargs)
+ return await asyncio.to_thread(func, *args, **kwargs)
return await anyio.to_thread.run_sync(
functools.partial(func, *args, **kwargs),
@@ -53,10 +28,7 @@ async def to_thread(
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
"""
Take a blocking function and create an async one that receives the same
- positional and keyword arguments. For python version 3.9 and above, it uses
- asyncio.to_thread to run the function in a separate thread. For python version
- 3.8, it uses locally defined copy of the asyncio.to_thread function which was
- introduced in python 3.9.
+ positional and keyword arguments.
Usage:
diff --git a/src/arcadepy/_version.py b/src/arcadepy/_version.py
index bb28e4b..3bd71df 100644
--- a/src/arcadepy/_version.py
+++ b/src/arcadepy/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "arcadepy"
-__version__ = "1.10.0" # x-release-please-version
+__version__ = "1.11.0" # x-release-please-version
diff --git a/src/arcadepy/resources/admin/secrets.py b/src/arcadepy/resources/admin/secrets.py
index ac839d9..b3d55f5 100644
--- a/src/arcadepy/resources/admin/secrets.py
+++ b/src/arcadepy/resources/admin/secrets.py
@@ -4,7 +4,8 @@
import httpx
-from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given
+from ..._types import Body, Omit, Query, Headers, NoneType, 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 (
@@ -13,7 +14,9 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ...types.admin import secret_create_params
from ..._base_client import make_request_options
+from ...types.admin.secret_response import SecretResponse
from ...types.admin.secret_list_response import SecretListResponse
__all__ = ["SecretsResource", "AsyncSecretsResource"]
@@ -39,6 +42,48 @@ def with_streaming_response(self) -> SecretsResourceWithStreamingResponse:
"""
return SecretsResourceWithStreamingResponse(self)
+ def create(
+ self,
+ secret_key: str,
+ *,
+ value: str,
+ description: str | 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,
+ ) -> SecretResponse:
+ """
+ Create or update a secret
+
+ 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 secret_key:
+ raise ValueError(f"Expected a non-empty value for `secret_key` but received {secret_key!r}")
+ return self._post(
+ f"/v1/admin/secrets/{secret_key}",
+ body=maybe_transform(
+ {
+ "value": value,
+ "description": description,
+ },
+ secret_create_params.SecretCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SecretResponse,
+ )
+
def list(
self,
*,
@@ -113,6 +158,48 @@ def with_streaming_response(self) -> AsyncSecretsResourceWithStreamingResponse:
"""
return AsyncSecretsResourceWithStreamingResponse(self)
+ async def create(
+ self,
+ secret_key: str,
+ *,
+ value: str,
+ description: str | 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,
+ ) -> SecretResponse:
+ """
+ Create or update a secret
+
+ 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 secret_key:
+ raise ValueError(f"Expected a non-empty value for `secret_key` but received {secret_key!r}")
+ return await self._post(
+ f"/v1/admin/secrets/{secret_key}",
+ body=await async_maybe_transform(
+ {
+ "value": value,
+ "description": description,
+ },
+ secret_create_params.SecretCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SecretResponse,
+ )
+
async def list(
self,
*,
@@ -171,6 +258,9 @@ class SecretsResourceWithRawResponse:
def __init__(self, secrets: SecretsResource) -> None:
self._secrets = secrets
+ self.create = to_raw_response_wrapper(
+ secrets.create,
+ )
self.list = to_raw_response_wrapper(
secrets.list,
)
@@ -183,6 +273,9 @@ class AsyncSecretsResourceWithRawResponse:
def __init__(self, secrets: AsyncSecretsResource) -> None:
self._secrets = secrets
+ self.create = async_to_raw_response_wrapper(
+ secrets.create,
+ )
self.list = async_to_raw_response_wrapper(
secrets.list,
)
@@ -195,6 +288,9 @@ class SecretsResourceWithStreamingResponse:
def __init__(self, secrets: SecretsResource) -> None:
self._secrets = secrets
+ self.create = to_streamed_response_wrapper(
+ secrets.create,
+ )
self.list = to_streamed_response_wrapper(
secrets.list,
)
@@ -207,6 +303,9 @@ class AsyncSecretsResourceWithStreamingResponse:
def __init__(self, secrets: AsyncSecretsResource) -> None:
self._secrets = secrets
+ self.create = async_to_streamed_response_wrapper(
+ secrets.create,
+ )
self.list = async_to_streamed_response_wrapper(
secrets.list,
)
diff --git a/src/arcadepy/resources/tools/formatted.py b/src/arcadepy/resources/tools/formatted.py
index 4b85016..3705e20 100644
--- a/src/arcadepy/resources/tools/formatted.py
+++ b/src/arcadepy/resources/tools/formatted.py
@@ -45,6 +45,7 @@ def list(
self,
*,
format: str | Omit = omit,
+ include_all_versions: bool | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
toolkit: str | Omit = omit,
@@ -63,6 +64,8 @@ def list(
Args:
format: Provider format
+ include_all_versions: Include all versions of each tool
+
limit: Number of items to return (default: 25, max: 100)
offset: Offset from the start of the list (default: 0)
@@ -90,6 +93,7 @@ def list(
query=maybe_transform(
{
"format": format,
+ "include_all_versions": include_all_versions,
"limit": limit,
"offset": offset,
"toolkit": toolkit,
@@ -175,6 +179,7 @@ def list(
self,
*,
format: str | Omit = omit,
+ include_all_versions: bool | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
toolkit: str | Omit = omit,
@@ -193,6 +198,8 @@ def list(
Args:
format: Provider format
+ include_all_versions: Include all versions of each tool
+
limit: Number of items to return (default: 25, max: 100)
offset: Offset from the start of the list (default: 0)
@@ -220,6 +227,7 @@ def list(
query=maybe_transform(
{
"format": format,
+ "include_all_versions": include_all_versions,
"limit": limit,
"offset": offset,
"toolkit": toolkit,
diff --git a/src/arcadepy/resources/tools/tools.py b/src/arcadepy/resources/tools/tools.py
index 30aec45..e45a77c 100644
--- a/src/arcadepy/resources/tools/tools.py
+++ b/src/arcadepy/resources/tools/tools.py
@@ -74,6 +74,7 @@ def with_streaming_response(self) -> ToolsResourceWithStreamingResponse:
def list(
self,
*,
+ include_all_versions: bool | Omit = omit,
include_format: List[Literal["arcade", "openai", "anthropic"]] | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
@@ -91,6 +92,8 @@ def list(
toolkit
Args:
+ include_all_versions: Include all versions of each tool
+
include_format: Comma separated tool formats that will be included in the response.
limit: Number of items to return (default: 25, max: 100)
@@ -119,6 +122,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "include_all_versions": include_all_versions,
"include_format": include_format,
"limit": limit,
"offset": offset,
@@ -319,6 +323,7 @@ def with_streaming_response(self) -> AsyncToolsResourceWithStreamingResponse:
def list(
self,
*,
+ include_all_versions: bool | Omit = omit,
include_format: List[Literal["arcade", "openai", "anthropic"]] | Omit = omit,
limit: int | Omit = omit,
offset: int | Omit = omit,
@@ -336,6 +341,8 @@ def list(
toolkit
Args:
+ include_all_versions: Include all versions of each tool
+
include_format: Comma separated tool formats that will be included in the response.
limit: Number of items to return (default: 25, max: 100)
@@ -364,6 +371,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "include_all_versions": include_all_versions,
"include_format": include_format,
"limit": limit,
"offset": offset,
diff --git a/src/arcadepy/types/admin/__init__.py b/src/arcadepy/types/admin/__init__.py
index 04e75d9..637035f 100644
--- a/src/arcadepy/types/admin/__init__.py
+++ b/src/arcadepy/types/admin/__init__.py
@@ -3,6 +3,7 @@
from __future__ import annotations
from .secret_response import SecretResponse as SecretResponse
+from .secret_create_params import SecretCreateParams as SecretCreateParams
from .secret_list_response import SecretListResponse as SecretListResponse
from .auth_provider_response import AuthProviderResponse as AuthProviderResponse
from .user_connection_response import UserConnectionResponse as UserConnectionResponse
diff --git a/src/arcadepy/types/admin/secret_create_params.py b/src/arcadepy/types/admin/secret_create_params.py
new file mode 100644
index 0000000..2986cbf
--- /dev/null
+++ b/src/arcadepy/types/admin/secret_create_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SecretCreateParams"]
+
+
+class SecretCreateParams(TypedDict, total=False):
+ value: Required[str]
+
+ description: str
diff --git a/src/arcadepy/types/chat/completion_create_params.py b/src/arcadepy/types/chat/completion_create_params.py
index 31608db..b9079d5 100644
--- a/src/arcadepy/types/chat/completion_create_params.py
+++ b/src/arcadepy/types/chat/completion_create_params.py
@@ -78,6 +78,8 @@ class ResponseFormat(TypedDict, total=False):
class StreamOptions(TypedDict, total=False):
+ """Options for streaming response. Only set this when you set stream: true."""
+
include_usage: bool
"""
If set, an additional chunk will be streamed before the data: [DONE] message.
diff --git a/src/arcadepy/types/tool_list_params.py b/src/arcadepy/types/tool_list_params.py
index 6a68b48..7984944 100644
--- a/src/arcadepy/types/tool_list_params.py
+++ b/src/arcadepy/types/tool_list_params.py
@@ -9,6 +9,9 @@
class ToolListParams(TypedDict, total=False):
+ include_all_versions: bool
+ """Include all versions of each tool"""
+
include_format: List[Literal["arcade", "openai", "anthropic"]]
"""Comma separated tool formats that will be included in the response."""
diff --git a/src/arcadepy/types/tools/formatted_list_params.py b/src/arcadepy/types/tools/formatted_list_params.py
index e9e53fd..97790de 100644
--- a/src/arcadepy/types/tools/formatted_list_params.py
+++ b/src/arcadepy/types/tools/formatted_list_params.py
@@ -11,6 +11,9 @@ class FormattedListParams(TypedDict, total=False):
format: str
"""Provider format"""
+ include_all_versions: bool
+ """Include all versions of each tool"""
+
limit: int
"""Number of items to return (default: 25, max: 100)"""
diff --git a/tests/api_resources/admin/test_secrets.py b/tests/api_resources/admin/test_secrets.py
index 3afbce3..f5e71b6 100644
--- a/tests/api_resources/admin/test_secrets.py
+++ b/tests/api_resources/admin/test_secrets.py
@@ -9,7 +9,7 @@
from arcadepy import Arcade, AsyncArcade
from tests.utils import assert_matches_type
-from arcadepy.types.admin import SecretListResponse
+from arcadepy.types.admin import SecretResponse, SecretListResponse
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -17,6 +17,57 @@
class TestSecrets:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+ @parametrize
+ def test_method_create(self, client: Arcade) -> None:
+ secret = client.admin.secrets.create(
+ secret_key="secret_key",
+ value="value",
+ )
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Arcade) -> None:
+ secret = client.admin.secrets.create(
+ secret_key="secret_key",
+ value="value",
+ description="description",
+ )
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Arcade) -> None:
+ response = client.admin.secrets.with_raw_response.create(
+ secret_key="secret_key",
+ value="value",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = response.parse()
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Arcade) -> None:
+ with client.admin.secrets.with_streaming_response.create(
+ secret_key="secret_key",
+ value="value",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = response.parse()
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create(self, client: Arcade) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `secret_key` but received ''"):
+ client.admin.secrets.with_raw_response.create(
+ secret_key="",
+ value="value",
+ )
+
@parametrize
def test_method_list(self, client: Arcade) -> None:
secret = client.admin.secrets.list()
@@ -86,6 +137,57 @@ class TestAsyncSecrets:
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
+ @parametrize
+ async def test_method_create(self, async_client: AsyncArcade) -> None:
+ secret = await async_client.admin.secrets.create(
+ secret_key="secret_key",
+ value="value",
+ )
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncArcade) -> None:
+ secret = await async_client.admin.secrets.create(
+ secret_key="secret_key",
+ value="value",
+ description="description",
+ )
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncArcade) -> None:
+ response = await async_client.admin.secrets.with_raw_response.create(
+ secret_key="secret_key",
+ value="value",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ secret = await response.parse()
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncArcade) -> None:
+ async with async_client.admin.secrets.with_streaming_response.create(
+ secret_key="secret_key",
+ value="value",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ secret = await response.parse()
+ assert_matches_type(SecretResponse, secret, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncArcade) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `secret_key` but received ''"):
+ await async_client.admin.secrets.with_raw_response.create(
+ secret_key="",
+ value="value",
+ )
+
@parametrize
async def test_method_list(self, async_client: AsyncArcade) -> None:
secret = await async_client.admin.secrets.list()
diff --git a/tests/api_resources/test_tools.py b/tests/api_resources/test_tools.py
index d3d1cb6..364d216 100644
--- a/tests/api_resources/test_tools.py
+++ b/tests/api_resources/test_tools.py
@@ -30,6 +30,7 @@ def test_method_list(self, client: Arcade) -> None:
@parametrize
def test_method_list_with_all_params(self, client: Arcade) -> None:
tool = client.tools.list(
+ include_all_versions=True,
include_format=["arcade"],
limit=0,
offset=0,
@@ -203,6 +204,7 @@ async def test_method_list(self, async_client: AsyncArcade) -> None:
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncArcade) -> None:
tool = await async_client.tools.list(
+ include_all_versions=True,
include_format=["arcade"],
limit=0,
offset=0,
diff --git a/tests/api_resources/tools/test_formatted.py b/tests/api_resources/tools/test_formatted.py
index 0ab89e6..271a233 100644
--- a/tests/api_resources/tools/test_formatted.py
+++ b/tests/api_resources/tools/test_formatted.py
@@ -26,6 +26,7 @@ def test_method_list(self, client: Arcade) -> None:
def test_method_list_with_all_params(self, client: Arcade) -> None:
formatted = client.tools.formatted.list(
format="format",
+ include_all_versions=True,
limit=0,
offset=0,
toolkit="toolkit",
@@ -115,6 +116,7 @@ async def test_method_list(self, async_client: AsyncArcade) -> None:
async def test_method_list_with_all_params(self, async_client: AsyncArcade) -> None:
formatted = await async_client.tools.formatted.list(
format="format",
+ include_all_versions=True,
limit=0,
offset=0,
toolkit="toolkit",
diff --git a/tests/test_models.py b/tests/test_models.py
index 202c517..62c67d8 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -9,7 +9,7 @@
from arcadepy._utils import PropertyInfo
from arcadepy._compat import PYDANTIC_V1, parse_obj, model_dump, model_json
-from arcadepy._models import BaseModel, construct_type
+from arcadepy._models import DISCRIMINATOR_CACHE, BaseModel, construct_type
class BasicModel(BaseModel):
@@ -809,7 +809,7 @@ class B(BaseModel):
UnionType = cast(Any, Union[A, B])
- assert not hasattr(UnionType, "__discriminator__")
+ assert not DISCRIMINATOR_CACHE.get(UnionType)
m = construct_type(
value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
@@ -818,7 +818,7 @@ class B(BaseModel):
assert m.type == "b"
assert m.data == "foo" # type: ignore[comparison-overlap]
- discriminator = UnionType.__discriminator__
+ discriminator = DISCRIMINATOR_CACHE.get(UnionType)
assert discriminator is not None
m = construct_type(
@@ -830,7 +830,7 @@ class B(BaseModel):
# if the discriminator details object stays the same between invocations then
# we hit the cache
- assert UnionType.__discriminator__ is discriminator
+ assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator
@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1")