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 @@ [![PyPI version](https://img.shields.io/pypi/v/arcadepy.svg?label=pypi%20(stable))](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