From 8ca7ff886773539906d2f214892409af7c77bc47 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 7 Nov 2025 17:47:38 +0000
Subject: [PATCH 01/16] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index d521f65..57791d3 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
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-46ea61822976f3993310e2c139f133f450b489682d8df4c61b65c731edba8639.yml
+openapi_spec_hash: 8cd802f4d9cdfa000d35792175b3b203
config_hash: 70cdb57c982c578d1961657c07b8b397
From d68084f663f4cf682457a195918d95cce6584772 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Nov 2025 04:49:51 +0000
Subject: [PATCH 02/16] chore(package): drop Python 3.8 support
---
README.md | 4 ++--
pyproject.toml | 5 ++---
src/arcadepy/_utils/_sync.py | 34 +++-------------------------------
3 files changed, 7 insertions(+), 36 deletions(-)
diff --git a/README.md b/README.md
index 0a1de1e..5589a18 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).
@@ -416,7 +416,7 @@ print(arcadepy.__version__)
## Requirements
-Python 3.8 or higher.
+Python 3.9 or higher.
## Contributing
diff --git a/pyproject.toml b/pyproject.toml
index c588719..fa0ea6e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,11 +15,10 @@ dependencies = [
"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",
@@ -141,7 +140,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/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:
From 4453f62ffe78f5bf6ec84c89566a2581ef381cca Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Nov 2025 04:50:28 +0000
Subject: [PATCH 03/16] fix: compat with Python 3.14
---
src/arcadepy/_models.py | 11 ++++++++---
tests/test_models.py | 8 ++++----
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index 6a3cd1d..fcec2cf 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 (
@@ -573,6 +574,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 +619,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 +674,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/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")
From d4527cf2a0ab18f31c0b75d6b452a09f47bd15f5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 12 Nov 2025 04:33:03 +0000
Subject: [PATCH 04/16] fix(compat): update signatures of `model_dump` and
`model_dump_json` for Pydantic v1
---
src/arcadepy/_models.py | 41 +++++++++++++++++++++++++++++------------
1 file changed, 29 insertions(+), 12 deletions(-)
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index fcec2cf..ca9500b 100644
--- a/src/arcadepy/_models.py
+++ b/src/arcadepy/_models.py
@@ -257,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
@@ -273,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.
@@ -299,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,
@@ -315,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:
@@ -355,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,
From 8c6d5c5ace8884839590c11a0b72f9ea70731e0e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 22 Nov 2025 04:15:50 +0000
Subject: [PATCH 05/16] chore: add Python 3.14 classifier and testing
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index fa0ea6e..864da50 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,6 +24,7 @@ classifiers = [
"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",
From e4be19ee70952d16361d55acfc082875bd926c01 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Nov 2025 03:27:15 +0000
Subject: [PATCH 06/16] fix: ensure streams are always closed
---
src/arcadepy/_streaming.py | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
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
From 0c9e3402e6714d2c1e4273ff5bd0e2dc00cce7b2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Nov 2025 03:28:21 +0000
Subject: [PATCH 07/16] chore(deps): mypy 1.18.1 has a regression, pin to 1.17
---
pyproject.toml | 2 +-
requirements-dev.lock | 4 +++-
requirements.lock | 8 ++++----
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 864da50..4ca4f40 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,7 +46,7 @@ managed = true
# version pins are in requirements-dev.lock
dev-dependencies = [
"pyright==1.1.399",
- "mypy",
+ "mypy==1.17",
"respx",
"pytest",
"pytest-asyncio",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 3c2bd65..2b8f8f0 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -72,7 +72,7 @@ mdurl==0.1.2
multidict==6.4.4
# via aiohttp
# via yarl
-mypy==1.14.1
+mypy==1.17.0
mypy-extensions==1.0.0
# via mypy
nodeenv==1.8.0
@@ -81,6 +81,8 @@ nox==2023.4.22
packaging==23.2
# via nox
# via pytest
+pathspec==0.12.1
+ # via mypy
platformdirs==3.11.0
# via virtualenv
pluggy==1.5.0
diff --git a/requirements.lock b/requirements.lock
index b5a9719..e946d7e 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -55,21 +55,21 @@ multidict==6.4.4
propcache==0.3.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
# via arcadepy
-typing-extensions==4.12.2
+typing-extensions==4.15.0
# via anyio
# via arcadepy
# 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
# via aiohttp
From d0dae695284c4ee2873529f0e24034dc409e99e7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 3 Dec 2025 05:36:26 +0000
Subject: [PATCH 08/16] chore: update lockfile
---
pyproject.toml | 14 +++---
requirements-dev.lock | 108 +++++++++++++++++++++++-------------------
requirements.lock | 31 ++++++------
3 files changed, 83 insertions(+), 70 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 4ca4f40..ce26232 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,14 +7,16 @@ 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.9"
classifiers = [
"Typing :: Typed",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 2b8f8f0..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,82 +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.17.0
-mypy-extensions==1.0.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
pathspec==0.12.1
# via mypy
-platformdirs==3.11.0
+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 e946d7e..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.12.5
# via arcadepy
pydantic-core==2.41.5
# via pydantic
-sniffio==1.3.0
- # via anyio
+sniffio==1.3.1
# via arcadepy
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.2
# via pydantic
-yarl==1.20.0
+yarl==1.22.0
# via aiohttp
From de0cde1a836d5b1992d8cdc284c3decbfa8eb20b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 3 Dec 2025 05:44:11 +0000
Subject: [PATCH 09/16] chore(docs): use environment variables for
authentication in code snippets
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 5589a18..80f0d66 100644
--- a/README.md
+++ b/README.md
@@ -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(
From 60e16d2e59496bcf64c28328e3a351925b82dccd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 3 Dec 2025 23:27:57 +0000
Subject: [PATCH 10/16] feat(api): api update
---
.stats.yml | 4 ++--
src/arcadepy/resources/tools/formatted.py | 8 ++++++++
src/arcadepy/resources/tools/tools.py | 8 ++++++++
src/arcadepy/types/tool_list_params.py | 3 +++
src/arcadepy/types/tools/formatted_list_params.py | 3 +++
tests/api_resources/test_tools.py | 2 ++
tests/api_resources/tools/test_formatted.py | 2 ++
7 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 57791d3..104433a 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-46ea61822976f3993310e2c139f133f450b489682d8df4c61b65c731edba8639.yml
-openapi_spec_hash: 8cd802f4d9cdfa000d35792175b3b203
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-4dc4e58ef402ce5362e0a8988b3928a8bfa0d5ba847f7ad8b14226a0cf282f28.yml
+openapi_spec_hash: fb72b9121306240c419669a3d42e45f6
config_hash: 70cdb57c982c578d1961657c07b8b397
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/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/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",
From 6c633443c6c61f409e1092693a30b5e4e2e31edd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Dec 2025 15:38:48 +0000
Subject: [PATCH 11/16] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index 104433a..4510020 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-4dc4e58ef402ce5362e0a8988b3928a8bfa0d5ba847f7ad8b14226a0cf282f28.yml
openapi_spec_hash: fb72b9121306240c419669a3d42e45f6
-config_hash: 70cdb57c982c578d1961657c07b8b397
+config_hash: f0d78fdab30e3346ae9b6804632ae8b6
From f98c6d3ccf44a38525f056bc52011fbdcc3183c1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 8 Dec 2025 17:44:52 +0000
Subject: [PATCH 12/16] feat(api): api update
---
.stats.yml | 4 +-
api.md | 1 +
src/arcadepy/resources/admin/secrets.py | 101 ++++++++++++++++-
src/arcadepy/types/admin/__init__.py | 1 +
.../types/admin/secret_create_params.py | 13 +++
tests/api_resources/admin/test_secrets.py | 104 +++++++++++++++++-
6 files changed, 220 insertions(+), 4 deletions(-)
create mode 100644 src/arcadepy/types/admin/secret_create_params.py
diff --git a/.stats.yml b/.stats.yml
index 4510020..09786b4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 29
+configured_endpoints: 30
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-4dc4e58ef402ce5362e0a8988b3928a8bfa0d5ba847f7ad8b14226a0cf282f28.yml
openapi_spec_hash: fb72b9121306240c419669a3d42e45f6
-config_hash: f0d78fdab30e3346ae9b6804632ae8b6
+config_hash: 7d1d98a4a1938a6884fa81ccfa3184c5
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/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/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/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()
From efb9e39b8d4675e9c869b70f4b1366792ea519d7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 01:38:48 +0000
Subject: [PATCH 13/16] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index 09786b4..7f6cedc 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 30
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-4dc4e58ef402ce5362e0a8988b3928a8bfa0d5ba847f7ad8b14226a0cf282f28.yml
openapi_spec_hash: fb72b9121306240c419669a3d42e45f6
-config_hash: 7d1d98a4a1938a6884fa81ccfa3184c5
+config_hash: b31a3f1bbe9abcc7bb144942d88ad1b6
From 6834686ece469c756682bb559f8bca5b5b316296 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 04:58:20 +0000
Subject: [PATCH 14/16] fix(types): allow pyright to infer TypedDict types
within SequenceNotStr
---
src/arcadepy/_types.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
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
From d8347917cfbe05f70e64d6c01ede460e15e20896 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 05:01:03 +0000
Subject: [PATCH 15/16] chore: add missing docstrings
---
src/arcadepy/types/chat/completion_create_params.py | 2 ++
1 file changed, 2 insertions(+)
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.
From aa408e070ba94c2c4597c29a1afff2ddee2c9006 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 05:01:20 +0000
Subject: [PATCH 16/16] release: 1.11.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 27 +++++++++++++++++++++++++++
pyproject.toml | 2 +-
src/arcadepy/_version.py | 2 +-
4 files changed, 30 insertions(+), 3 deletions(-)
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/CHANGELOG.md b/CHANGELOG.md
index 5938671..63675fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,32 @@
# Changelog
+## 1.11.0 (2025-12-09)
+
+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))
+
+
+### 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))
+* **package:** drop Python 3.8 support ([d68084f](https://github.com/ArcadeAI/arcade-py/commit/d68084f663f4cf682457a195918d95cce6584772))
+* 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/pyproject.toml b/pyproject.toml
index ce26232..2eed60c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "arcadepy"
-version = "1.10.0"
+version = "1.11.0"
description = "The official Python library for the Arcade API"
dynamic = ["readme"]
license = "MIT"
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