From 52f818d5bdee294add53ebc8257c6d3f48cbdd65 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 04:47:20 +0000
Subject: [PATCH 01/40] docs: revise readme docs about nested params (#128)
---
README.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/README.md b/README.md
index 90889480..15dd1a93 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,27 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
+## Nested params
+
+Nested parameters are dictionaries, typed using `TypedDict`, for example:
+
+```python
+from arcadepy import Arcade
+
+client = Arcade()
+
+authorization_response = client.auth.authorize(
+ auth_requirement={
+ "id": "id",
+ "oauth2": {"scopes": ["string"]},
+ "provider_id": "provider_id",
+ "provider_type": "provider_type",
+ },
+ user_id="user_id",
+)
+print(authorization_response.auth_requirement)
+```
+
## Handling errors
When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `arcadepy.APIConnectionError` is raised.
From 8ea43896f5f255000cda00f1f25b9ab03697bc4e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 05:15:41 +0000
Subject: [PATCH 02/40] test: add DEFER_PYDANTIC_BUILD=false flag to tests
(#130)
---
scripts/test | 2 ++
1 file changed, 2 insertions(+)
diff --git a/scripts/test b/scripts/test
index 4fa5698b..2b878456 100755
--- a/scripts/test
+++ b/scripts/test
@@ -52,6 +52,8 @@ else
echo
fi
+export DEFER_PYDANTIC_BUILD=false
+
echo "==> Running tests"
rye run pytest "$@"
From 4a0f4094477d25a8c213f07e33f42296864d2866 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 14 Mar 2025 03:37:50 +0000
Subject: [PATCH 03/40] chore(internal): remove extra empty newlines (#131)
---
pyproject.toml | 2 --
1 file changed, 2 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 03ab7607..c7460bbe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,7 +38,6 @@ Homepage = "https://github.com/ArcadeAI/arcade-py"
Repository = "https://github.com/ArcadeAI/arcade-py"
-
[tool.rye]
managed = true
# version pins are in requirements-dev.lock
@@ -152,7 +151,6 @@ reportImplicitOverride = true
reportImportCycles = false
reportPrivateUsage = false
-
[tool.ruff]
line-length = 120
output-format = "grouped"
From 85ff426032303355d1d8c331813aaeeeaef0f69d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 15 Mar 2025 02:51:32 +0000
Subject: [PATCH 04/40] chore(internal): codegen related update (#132)
---
requirements-dev.lock | 1 +
requirements.lock | 1 +
2 files changed, 2 insertions(+)
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 65cd1c61..75552309 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -7,6 +7,7 @@
# all-features: true
# with-sources: false
# generate-hashes: false
+# universal: false
-e file:.
annotated-types==0.6.0
diff --git a/requirements.lock b/requirements.lock
index 39dd71d6..4abe5db2 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -7,6 +7,7 @@
# all-features: true
# with-sources: false
# generate-hashes: false
+# universal: false
-e file:.
annotated-types==0.6.0
From 0068d951be52d203c558d6a241feca26eb1f3613 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 15 Mar 2025 03:07:38 +0000
Subject: [PATCH 05/40] chore(internal): bump rye to 0.44.0 (#133)
---
.devcontainer/Dockerfile | 2 +-
.github/workflows/ci.yml | 4 ++--
.github/workflows/publish-pypi.yml | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 55d20255..ff261bad 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
USER vscode
-RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash
+RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash
ENV PATH=/home/vscode/.rye/shims:$PATH
RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8a8a4f7..3b286e5a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Install dependencies
@@ -42,7 +42,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Bootstrap
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index e68c2875..0080ea4c 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -21,7 +21,7 @@ jobs:
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
- RYE_VERSION: '0.35.0'
+ RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Publish to PyPI
From 2e8aa5469824223540f3c1f970662bf0ed5f62ac Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 15 Mar 2025 03:15:33 +0000
Subject: [PATCH 06/40] fix(types): handle more discriminated union shapes
(#134)
---
src/arcadepy/_models.py | 7 +++++--
tests/test_models.py | 32 ++++++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index c4401ff8..b51a1bf5 100644
--- a/src/arcadepy/_models.py
+++ b/src/arcadepy/_models.py
@@ -65,7 +65,7 @@
from ._constants import RAW_RESPONSE_HEADER
if TYPE_CHECKING:
- from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema
+ from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
__all__ = ["BaseModel", "GenericModel"]
@@ -646,15 +646,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
schema = model.__pydantic_core_schema__
+ if schema["type"] == "definitions":
+ schema = schema["schema"]
+
if schema["type"] != "model":
return None
+ schema = cast("ModelSchema", schema)
fields_schema = schema["schema"]
if fields_schema["type"] != "model-fields":
return None
fields_schema = cast("ModelFieldsSchema", fields_schema)
-
field = fields_schema["fields"].get(field_name)
if not field:
return None
diff --git a/tests/test_models.py b/tests/test_models.py
index bd512ab8..da2f1234 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -854,3 +854,35 @@ class Model(BaseModel):
m = construct_type(value={"cls": "foo"}, type_=Model)
assert isinstance(m, Model)
assert isinstance(m.cls, str)
+
+
+def test_discriminated_union_case() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: bool
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: List[Union[A, object]]
+
+ class ModelA(BaseModel):
+ type: Literal["modelA"]
+
+ data: int
+
+ class ModelB(BaseModel):
+ type: Literal["modelB"]
+
+ required: str
+
+ data: Union[A, B]
+
+ # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required`
+ m = construct_type(
+ value={"type": "modelB", "data": {"type": "a", "data": True}},
+ type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]),
+ )
+
+ assert isinstance(m, ModelB)
From fd63bd10897027f3a2ea9e82043943213c1f897f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 17 Mar 2025 16:22:10 +0000
Subject: [PATCH 07/40] fix(ci): ensure pip is always available (#135)
---
bin/publish-pypi | 1 +
1 file changed, 1 insertion(+)
diff --git a/bin/publish-pypi b/bin/publish-pypi
index 05bfccbb..ebebf916 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -5,5 +5,6 @@ mkdir -p dist
rye build --clean
# Patching importlib-metadata version until upstream library version is updated
# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
+"$HOME/.rye/self/bin/python3" -m ensurepip
"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
rye publish --yes --token=$PYPI_TOKEN
From bd4bfc8d40a1a59ac65e862eb6f0e04e0184c75d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 17 Mar 2025 16:29:30 +0000
Subject: [PATCH 08/40] fix(ci): remove publishing patch (#136)
---
bin/publish-pypi | 4 ----
pyproject.toml | 2 +-
2 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/bin/publish-pypi b/bin/publish-pypi
index ebebf916..826054e9 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -3,8 +3,4 @@
set -eux
mkdir -p dist
rye build --clean
-# Patching importlib-metadata version until upstream library version is updated
-# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
-"$HOME/.rye/self/bin/python3" -m ensurepip
-"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
rye publish --yes --token=$PYPI_TOKEN
diff --git a/pyproject.toml b/pyproject.toml
index c7460bbe..d3cf097e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -86,7 +86,7 @@ typecheck = { chain = [
"typecheck:mypy" = "mypy ."
[build-system]
-requires = ["hatchling", "hatch-fancy-pypi-readme"]
+requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[tool.hatch.build]
From dea5dac48883ce791c6a4280d0f162b8da36ce6f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 21 Mar 2025 19:46:51 +0000
Subject: [PATCH 09/40] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index 9a0cc5e6..2b9d2660 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-3c7443a5e05ad4ade2ac36325d1def05cb3842bb53a180fc76feb565ea875cc7.yml
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-e0b226d400563e77eb480ed532a4a42e6594f2932d030a906147c2917595af05.yml
From 7ce1f6aee46e2d8763c194a88f28428df9eb6d5f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 26 Mar 2025 22:59:51 +0000
Subject: [PATCH 10/40] feat(api): api update (#137)
---
.stats.yml | 4 ++-
src/arcadepy/_models.py | 2 +-
src/arcadepy/_utils/_transform.py | 2 +-
src/arcadepy/resources/workers.py | 16 ++++++++--
src/arcadepy/types/worker_create_params.py | 16 ++++++++--
src/arcadepy/types/worker_response.py | 36 ++++++++++++++++++++--
src/arcadepy/types/worker_update_params.py | 12 +++++++-
tests/api_resources/test_workers.py | 34 ++++++++++++++++----
8 files changed, 106 insertions(+), 16 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 2b9d2660..1a0b8be9 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-e0b226d400563e77eb480ed532a4a42e6594f2932d030a906147c2917595af05.yml
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-d4269d3e7013741eeb30221bb022d65c5d81d7ed0edc06980eccf7c83fcb48a9.yml
+openapi_spec_hash: 5661031e615bccd01256d82705ae27cc
+config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index b51a1bf5..34935716 100644
--- a/src/arcadepy/_models.py
+++ b/src/arcadepy/_models.py
@@ -681,7 +681,7 @@ def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
setattr(typ, "__pydantic_config__", config) # noqa: B010
-# our use of subclasssing here causes weirdness for type checkers,
+# our use of subclassing here causes weirdness for type checkers,
# so we just pretend that we don't subclass
if TYPE_CHECKING:
GenericModel = BaseModel
diff --git a/src/arcadepy/_utils/_transform.py b/src/arcadepy/_utils/_transform.py
index 18afd9d8..7ac2e17f 100644
--- a/src/arcadepy/_utils/_transform.py
+++ b/src/arcadepy/_utils/_transform.py
@@ -126,7 +126,7 @@ def _get_annotated_type(type_: type) -> type | None:
def _maybe_transform_key(key: str, type_: type) -> str:
"""Transform the given `data` based on the annotations provided in `type_`.
- Note: this function only looks at `Annotated` types that contain `PropertInfo` metadata.
+ Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
"""
annotated_type = _get_annotated_type(type_)
if annotated_type is None:
diff --git a/src/arcadepy/resources/workers.py b/src/arcadepy/resources/workers.py
index 05893630..986d5c1e 100644
--- a/src/arcadepy/resources/workers.py
+++ b/src/arcadepy/resources/workers.py
@@ -51,8 +51,10 @@ def create(
self,
*,
id: str,
- enabled: bool,
+ type: str,
+ enabled: bool | NotGiven = NOT_GIVEN,
http: worker_create_params.HTTP | NotGiven = NOT_GIVEN,
+ mcp: worker_create_params.Mcp | NotGiven = NOT_GIVEN,
# 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,
@@ -77,8 +79,10 @@ def create(
body=maybe_transform(
{
"id": id,
+ "type": type,
"enabled": enabled,
"http": http,
+ "mcp": mcp,
},
worker_create_params.WorkerCreateParams,
),
@@ -94,6 +98,7 @@ def update(
*,
enabled: bool | NotGiven = NOT_GIVEN,
http: worker_update_params.HTTP | NotGiven = NOT_GIVEN,
+ mcp: worker_update_params.Mcp | NotGiven = NOT_GIVEN,
# 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,
@@ -121,6 +126,7 @@ def update(
{
"enabled": enabled,
"http": http,
+ "mcp": mcp,
},
worker_update_params.WorkerUpdateParams,
),
@@ -352,8 +358,10 @@ async def create(
self,
*,
id: str,
- enabled: bool,
+ type: str,
+ enabled: bool | NotGiven = NOT_GIVEN,
http: worker_create_params.HTTP | NotGiven = NOT_GIVEN,
+ mcp: worker_create_params.Mcp | NotGiven = NOT_GIVEN,
# 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,
@@ -378,8 +386,10 @@ async def create(
body=await async_maybe_transform(
{
"id": id,
+ "type": type,
"enabled": enabled,
"http": http,
+ "mcp": mcp,
},
worker_create_params.WorkerCreateParams,
),
@@ -395,6 +405,7 @@ async def update(
*,
enabled: bool | NotGiven = NOT_GIVEN,
http: worker_update_params.HTTP | NotGiven = NOT_GIVEN,
+ mcp: worker_update_params.Mcp | NotGiven = NOT_GIVEN,
# 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,
@@ -422,6 +433,7 @@ async def update(
{
"enabled": enabled,
"http": http,
+ "mcp": mcp,
},
worker_update_params.WorkerUpdateParams,
),
diff --git a/src/arcadepy/types/worker_create_params.py b/src/arcadepy/types/worker_create_params.py
index d08266e3..df77dded 100644
--- a/src/arcadepy/types/worker_create_params.py
+++ b/src/arcadepy/types/worker_create_params.py
@@ -4,16 +4,20 @@
from typing_extensions import Required, TypedDict
-__all__ = ["WorkerCreateParams", "HTTP"]
+__all__ = ["WorkerCreateParams", "HTTP", "Mcp"]
class WorkerCreateParams(TypedDict, total=False):
id: Required[str]
- enabled: Required[bool]
+ type: Required[str]
+
+ enabled: bool
http: HTTP
+ mcp: Mcp
+
class HTTP(TypedDict, total=False):
retry: Required[int]
@@ -23,3 +27,11 @@ class HTTP(TypedDict, total=False):
timeout: Required[int]
uri: Required[str]
+
+
+class Mcp(TypedDict, total=False):
+ retry: Required[int]
+
+ timeout: Required[int]
+
+ uri: Required[str]
diff --git a/src/arcadepy/types/worker_response.py b/src/arcadepy/types/worker_response.py
index 0f7f434d..27722a20 100644
--- a/src/arcadepy/types/worker_response.py
+++ b/src/arcadepy/types/worker_response.py
@@ -5,7 +5,7 @@
from .._models import BaseModel
-__all__ = ["WorkerResponse", "HTTP", "HTTPSecret"]
+__all__ = ["WorkerResponse", "HTTP", "HTTPSecret", "Mcp", "Oxp", "OxpSecret"]
class HTTPSecret(BaseModel):
@@ -30,6 +30,36 @@ class HTTP(BaseModel):
uri: Optional[str] = None
+class Mcp(BaseModel):
+ retry: Optional[int] = None
+
+ timeout: Optional[int] = None
+
+ uri: Optional[str] = None
+
+
+class OxpSecret(BaseModel):
+ binding: Optional[Literal["static", "tenant", "organization", "account"]] = None
+
+ editable: Optional[bool] = None
+
+ exists: Optional[bool] = None
+
+ hint: Optional[str] = None
+
+ value: Optional[str] = None
+
+
+class Oxp(BaseModel):
+ retry: Optional[int] = None
+
+ secret: Optional[OxpSecret] = None
+
+ timeout: Optional[int] = None
+
+ uri: Optional[str] = None
+
+
class WorkerResponse(BaseModel):
id: Optional[str] = None
@@ -37,4 +67,6 @@ class WorkerResponse(BaseModel):
http: Optional[HTTP] = None
- type: Optional[str] = None
+ mcp: Optional[Mcp] = None
+
+ oxp: Optional[Oxp] = None
diff --git a/src/arcadepy/types/worker_update_params.py b/src/arcadepy/types/worker_update_params.py
index 0215ac17..a07fbb69 100644
--- a/src/arcadepy/types/worker_update_params.py
+++ b/src/arcadepy/types/worker_update_params.py
@@ -4,7 +4,7 @@
from typing_extensions import TypedDict
-__all__ = ["WorkerUpdateParams", "HTTP"]
+__all__ = ["WorkerUpdateParams", "HTTP", "Mcp"]
class WorkerUpdateParams(TypedDict, total=False):
@@ -12,6 +12,8 @@ class WorkerUpdateParams(TypedDict, total=False):
http: HTTP
+ mcp: Mcp
+
class HTTP(TypedDict, total=False):
retry: int
@@ -21,3 +23,11 @@ class HTTP(TypedDict, total=False):
timeout: int
uri: str
+
+
+class Mcp(TypedDict, total=False):
+ retry: int
+
+ timeout: int
+
+ uri: str
diff --git a/tests/api_resources/test_workers.py b/tests/api_resources/test_workers.py
index ab065073..0a300735 100644
--- a/tests/api_resources/test_workers.py
+++ b/tests/api_resources/test_workers.py
@@ -26,7 +26,7 @@ class TestWorkers:
def test_method_create(self, client: Arcade) -> None:
worker = client.workers.create(
id="id",
- enabled=True,
+ type="type",
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -34,6 +34,7 @@ def test_method_create(self, client: Arcade) -> None:
def test_method_create_with_all_params(self, client: Arcade) -> None:
worker = client.workers.create(
id="id",
+ type="type",
enabled=True,
http={
"retry": 0,
@@ -41,6 +42,11 @@ def test_method_create_with_all_params(self, client: Arcade) -> None:
"timeout": 1,
"uri": "uri",
},
+ mcp={
+ "retry": 0,
+ "timeout": 1,
+ "uri": "uri",
+ },
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -48,7 +54,7 @@ def test_method_create_with_all_params(self, client: Arcade) -> None:
def test_raw_response_create(self, client: Arcade) -> None:
response = client.workers.with_raw_response.create(
id="id",
- enabled=True,
+ type="type",
)
assert response.is_closed is True
@@ -60,7 +66,7 @@ def test_raw_response_create(self, client: Arcade) -> None:
def test_streaming_response_create(self, client: Arcade) -> None:
with client.workers.with_streaming_response.create(
id="id",
- enabled=True,
+ type="type",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -88,6 +94,11 @@ def test_method_update_with_all_params(self, client: Arcade) -> None:
"timeout": 1,
"uri": "uri",
},
+ mcp={
+ "retry": 0,
+ "timeout": 1,
+ "uri": "uri",
+ },
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -324,7 +335,7 @@ class TestAsyncWorkers:
async def test_method_create(self, async_client: AsyncArcade) -> None:
worker = await async_client.workers.create(
id="id",
- enabled=True,
+ type="type",
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -332,6 +343,7 @@ async def test_method_create(self, async_client: AsyncArcade) -> None:
async def test_method_create_with_all_params(self, async_client: AsyncArcade) -> None:
worker = await async_client.workers.create(
id="id",
+ type="type",
enabled=True,
http={
"retry": 0,
@@ -339,6 +351,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncArcade) ->
"timeout": 1,
"uri": "uri",
},
+ mcp={
+ "retry": 0,
+ "timeout": 1,
+ "uri": "uri",
+ },
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -346,7 +363,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncArcade) ->
async def test_raw_response_create(self, async_client: AsyncArcade) -> None:
response = await async_client.workers.with_raw_response.create(
id="id",
- enabled=True,
+ type="type",
)
assert response.is_closed is True
@@ -358,7 +375,7 @@ async def test_raw_response_create(self, async_client: AsyncArcade) -> None:
async def test_streaming_response_create(self, async_client: AsyncArcade) -> None:
async with async_client.workers.with_streaming_response.create(
id="id",
- enabled=True,
+ type="type",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -386,6 +403,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncArcade) ->
"timeout": 1,
"uri": "uri",
},
+ mcp={
+ "retry": 0,
+ "timeout": 1,
+ "uri": "uri",
+ },
)
assert_matches_type(WorkerResponse, worker, path=["response"])
From cb71d87280889f8d1200be0d069a088560c1bc8b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 19:19:17 +0000
Subject: [PATCH 11/40] feat(api): api update (#138)
---
.stats.yml | 4 ++--
src/arcadepy/types/worker_response.py | 12 +++++++++++-
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 1a0b8be9..1cab3a3a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-d4269d3e7013741eeb30221bb022d65c5d81d7ed0edc06980eccf7c83fcb48a9.yml
-openapi_spec_hash: 5661031e615bccd01256d82705ae27cc
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-908af5d6d92eff24b930334d7fc1cfbdf96e2b686ec15b3b0cca96e7fdca43f2.yml
+openapi_spec_hash: 07ef7f3f0c6cae5e3373e9af292a6acf
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/src/arcadepy/types/worker_response.py b/src/arcadepy/types/worker_response.py
index 27722a20..85c899bc 100644
--- a/src/arcadepy/types/worker_response.py
+++ b/src/arcadepy/types/worker_response.py
@@ -5,7 +5,13 @@
from .._models import BaseModel
-__all__ = ["WorkerResponse", "HTTP", "HTTPSecret", "Mcp", "Oxp", "OxpSecret"]
+__all__ = ["WorkerResponse", "Binding", "HTTP", "HTTPSecret", "Mcp", "Oxp", "OxpSecret"]
+
+
+class Binding(BaseModel):
+ id: Optional[str] = None
+
+ type: Optional[Literal["static", "tenant", "organization", "account"]] = None
class HTTPSecret(BaseModel):
@@ -63,6 +69,8 @@ class Oxp(BaseModel):
class WorkerResponse(BaseModel):
id: Optional[str] = None
+ binding: Optional[Binding] = None
+
enabled: Optional[bool] = None
http: Optional[HTTP] = None
@@ -70,3 +78,5 @@ class WorkerResponse(BaseModel):
mcp: Optional[Mcp] = None
oxp: Optional[Oxp] = None
+
+ type: Optional[Literal["http", "mcp", "unknown"]] = None
From 9d4474dc84b911bd94b5a13da7fa6abb126c002d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 20:43:46 +0000
Subject: [PATCH 12/40] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 1cab3a3a..3e842c3d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-908af5d6d92eff24b930334d7fc1cfbdf96e2b686ec15b3b0cca96e7fdca43f2.yml
-openapi_spec_hash: 07ef7f3f0c6cae5e3373e9af292a6acf
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-6d453e96e34a36ecb9e215d646d7102302edb0fd495191f44fb1a1506d9cbb8f.yml
+openapi_spec_hash: a68cd77477bac3d0c50351e2a174ae50
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
From 9a0ef5a37cd20950fde494f690dd403715029243 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 1 Apr 2025 00:40:16 +0000
Subject: [PATCH 13/40] feat(api): api update (#139)
---
.stats.yml | 4 ++--
src/arcadepy/resources/workers.py | 8 ++++----
src/arcadepy/types/worker_create_params.py | 4 ++--
tests/api_resources/test_workers.py | 10 ++--------
4 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 3e842c3d..3921d93f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-6d453e96e34a36ecb9e215d646d7102302edb0fd495191f44fb1a1506d9cbb8f.yml
-openapi_spec_hash: a68cd77477bac3d0c50351e2a174ae50
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-992c4cfe194f4e4bfed0877e15c601e0577b661c779bb1b28d7d0531bba03677.yml
+openapi_spec_hash: 2a2799549615cc0b9b6d5dcc83441d15
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/src/arcadepy/resources/workers.py b/src/arcadepy/resources/workers.py
index 986d5c1e..78904108 100644
--- a/src/arcadepy/resources/workers.py
+++ b/src/arcadepy/resources/workers.py
@@ -51,10 +51,10 @@ def create(
self,
*,
id: str,
- type: str,
enabled: bool | NotGiven = NOT_GIVEN,
http: worker_create_params.HTTP | NotGiven = NOT_GIVEN,
mcp: worker_create_params.Mcp | NotGiven = NOT_GIVEN,
+ type: str | NotGiven = NOT_GIVEN,
# 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,
@@ -79,10 +79,10 @@ def create(
body=maybe_transform(
{
"id": id,
- "type": type,
"enabled": enabled,
"http": http,
"mcp": mcp,
+ "type": type,
},
worker_create_params.WorkerCreateParams,
),
@@ -358,10 +358,10 @@ async def create(
self,
*,
id: str,
- type: str,
enabled: bool | NotGiven = NOT_GIVEN,
http: worker_create_params.HTTP | NotGiven = NOT_GIVEN,
mcp: worker_create_params.Mcp | NotGiven = NOT_GIVEN,
+ type: str | NotGiven = NOT_GIVEN,
# 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,
@@ -386,10 +386,10 @@ async def create(
body=await async_maybe_transform(
{
"id": id,
- "type": type,
"enabled": enabled,
"http": http,
"mcp": mcp,
+ "type": type,
},
worker_create_params.WorkerCreateParams,
),
diff --git a/src/arcadepy/types/worker_create_params.py b/src/arcadepy/types/worker_create_params.py
index df77dded..00c69663 100644
--- a/src/arcadepy/types/worker_create_params.py
+++ b/src/arcadepy/types/worker_create_params.py
@@ -10,14 +10,14 @@
class WorkerCreateParams(TypedDict, total=False):
id: Required[str]
- type: Required[str]
-
enabled: bool
http: HTTP
mcp: Mcp
+ type: str
+
class HTTP(TypedDict, total=False):
retry: Required[int]
diff --git a/tests/api_resources/test_workers.py b/tests/api_resources/test_workers.py
index 0a300735..155e1c09 100644
--- a/tests/api_resources/test_workers.py
+++ b/tests/api_resources/test_workers.py
@@ -26,7 +26,6 @@ class TestWorkers:
def test_method_create(self, client: Arcade) -> None:
worker = client.workers.create(
id="id",
- type="type",
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -34,7 +33,6 @@ def test_method_create(self, client: Arcade) -> None:
def test_method_create_with_all_params(self, client: Arcade) -> None:
worker = client.workers.create(
id="id",
- type="type",
enabled=True,
http={
"retry": 0,
@@ -47,6 +45,7 @@ def test_method_create_with_all_params(self, client: Arcade) -> None:
"timeout": 1,
"uri": "uri",
},
+ type="type",
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -54,7 +53,6 @@ def test_method_create_with_all_params(self, client: Arcade) -> None:
def test_raw_response_create(self, client: Arcade) -> None:
response = client.workers.with_raw_response.create(
id="id",
- type="type",
)
assert response.is_closed is True
@@ -66,7 +64,6 @@ def test_raw_response_create(self, client: Arcade) -> None:
def test_streaming_response_create(self, client: Arcade) -> None:
with client.workers.with_streaming_response.create(
id="id",
- type="type",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -335,7 +332,6 @@ class TestAsyncWorkers:
async def test_method_create(self, async_client: AsyncArcade) -> None:
worker = await async_client.workers.create(
id="id",
- type="type",
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -343,7 +339,6 @@ async def test_method_create(self, async_client: AsyncArcade) -> None:
async def test_method_create_with_all_params(self, async_client: AsyncArcade) -> None:
worker = await async_client.workers.create(
id="id",
- type="type",
enabled=True,
http={
"retry": 0,
@@ -356,6 +351,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncArcade) ->
"timeout": 1,
"uri": "uri",
},
+ type="type",
)
assert_matches_type(WorkerResponse, worker, path=["response"])
@@ -363,7 +359,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncArcade) ->
async def test_raw_response_create(self, async_client: AsyncArcade) -> None:
response = await async_client.workers.with_raw_response.create(
id="id",
- type="type",
)
assert response.is_closed is True
@@ -375,7 +370,6 @@ async def test_raw_response_create(self, async_client: AsyncArcade) -> None:
async def test_streaming_response_create(self, async_client: AsyncArcade) -> None:
async with async_client.workers.with_streaming_response.create(
id="id",
- type="type",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
From 850838edc42110c22d2c4342e4340f8322fb0e86 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 04:47:18 +0000
Subject: [PATCH 14/40] chore(internal): remove trailing character (#140)
---
tests/test_client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_client.py b/tests/test_client.py
index 7a0b0109..19e02d8b 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1663,7 +1663,7 @@ def test_get_platform(self) -> None:
import threading
from arcadepy._utils import asyncify
- from arcadepy._base_client import get_platform
+ from arcadepy._base_client import get_platform
async def test_main() -> None:
result = await asyncify(get_platform)()
From fe028d08aa5db60e540bca6f0635911ffcb79486 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 5 Apr 2025 02:11:49 +0000
Subject: [PATCH 15/40] docs: swap examples used in readme (#141)
---
README.md | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 15dd1a93..d55f97ed 100644
--- a/README.md
+++ b/README.md
@@ -90,16 +90,10 @@ from arcadepy import Arcade
client = Arcade()
-authorization_response = client.auth.authorize(
- auth_requirement={
- "id": "id",
- "oauth2": {"scopes": ["string"]},
- "provider_id": "provider_id",
- "provider_type": "provider_type",
- },
- user_id="user_id",
+chat_response = client.chat.completions.create(
+ response_format={"type": "json_object"},
)
-print(authorization_response.auth_requirement)
+print(chat_response.response_format)
```
## Handling errors
From af27cc24050a658f27d49909b0596b41309c1326 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 9 Apr 2025 02:16:03 +0000
Subject: [PATCH 16/40] chore(internal): slight transform perf improvement
(#142)
---
src/arcadepy/_utils/_transform.py | 22 ++++++++++++++++++++++
tests/test_transform.py | 12 ++++++++++++
2 files changed, 34 insertions(+)
diff --git a/src/arcadepy/_utils/_transform.py b/src/arcadepy/_utils/_transform.py
index 7ac2e17f..3ec62081 100644
--- a/src/arcadepy/_utils/_transform.py
+++ b/src/arcadepy/_utils/_transform.py
@@ -142,6 +142,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
return key
+def _no_transform_needed(annotation: type) -> bool:
+ return annotation == float or annotation == int
+
+
def _transform_recursive(
data: object,
*,
@@ -184,6 +188,15 @@ def _transform_recursive(
return cast(object, data)
inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
if is_union_type(stripped_type):
@@ -332,6 +345,15 @@ async def _async_transform_recursive(
return cast(object, data)
inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
if is_union_type(stripped_type):
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 9029329f..843bfd05 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -432,3 +432,15 @@ async def test_base64_file_input(use_async: bool) -> None:
assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
"foo": "SGVsbG8sIHdvcmxkIQ=="
} # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_transform_skipping(use_async: bool) -> None:
+ # lists of ints are left as-is
+ data = [1, 2, 3]
+ assert await transform(data, List[int], use_async) is data
+
+ # iterables of ints are converted to a list
+ data = iter([1, 2, 3])
+ assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
From 02cc2952f5b3bc36645d1da528f46ac65b681fd5 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 02:20:59 +0000
Subject: [PATCH 17/40] chore(internal): expand CI branch coverage
---
.github/workflows/ci.yml | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3b286e5a..53a3a09c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,18 @@
name: CI
on:
push:
- branches:
- - main
- pull_request:
- branches:
- - main
- - next
+ branches-ignore:
+ - 'generated'
+ - 'codegen/**'
+ - 'integrated/**'
+ - 'preview-head/**'
+ - 'preview-base/**'
+ - 'preview/**'
jobs:
lint:
name: lint
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
@@ -33,7 +33,6 @@ jobs:
test:
name: test
runs-on: ubuntu-latest
-
steps:
- uses: actions/checkout@v4
From f6d1892c845ee924614c174ef5bd24241b111c8c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 10 Apr 2025 02:24:07 +0000
Subject: [PATCH 18/40] chore(internal): reduce CI branch coverage
---
.github/workflows/ci.yml | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 53a3a09c..81f6dc20 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,13 +1,12 @@
name: CI
on:
push:
- branches-ignore:
- - 'generated'
- - 'codegen/**'
- - 'integrated/**'
- - 'preview-head/**'
- - 'preview-base/**'
- - 'preview/**'
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ - next
jobs:
lint:
From af97129e7ce0dc09566e4166cbaaa2b6a2246864 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Apr 2025 02:20:37 +0000
Subject: [PATCH 19/40] fix(perf): skip traversing types for NotGiven values
---
src/arcadepy/_utils/_transform.py | 11 +++++++++++
tests/test_transform.py | 9 ++++++++-
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/arcadepy/_utils/_transform.py b/src/arcadepy/_utils/_transform.py
index 3ec62081..3b2b8e00 100644
--- a/src/arcadepy/_utils/_transform.py
+++ b/src/arcadepy/_utils/_transform.py
@@ -12,6 +12,7 @@
from ._utils import (
is_list,
+ is_given,
is_mapping,
is_iterable,
)
@@ -258,6 +259,11 @@ def _transform_typeddict(
result: dict[str, object] = {}
annotations = get_type_hints(expected_type, include_extras=True)
for key, value in data.items():
+ if not is_given(value):
+ # we don't need to include `NotGiven` values here as they'll
+ # be stripped out before the request is sent anyway
+ continue
+
type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
@@ -415,6 +421,11 @@ async def _async_transform_typeddict(
result: dict[str, object] = {}
annotations = get_type_hints(expected_type, include_extras=True)
for key, value in data.items():
+ if not is_given(value):
+ # we don't need to include `NotGiven` values here as they'll
+ # be stripped out before the request is sent anyway
+ continue
+
type_ = annotations.get(key)
if type_ is None:
# we do not have a type annotation for this field, leave it as is
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 843bfd05..c6bb27cf 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -8,7 +8,7 @@
import pytest
-from arcadepy._types import Base64FileInput
+from arcadepy._types import NOT_GIVEN, Base64FileInput
from arcadepy._utils import (
PropertyInfo,
transform as _transform,
@@ -444,3 +444,10 @@ async def test_transform_skipping(use_async: bool) -> None:
# iterables of ints are converted to a list
data = iter([1, 2, 3])
assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_strips_notgiven(use_async: bool) -> None:
+ assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
+ assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}
From c544b05d5afac34fe9119b791add080567f0e4cf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 12 Apr 2025 02:21:40 +0000
Subject: [PATCH 20/40] fix(perf): optimize some hot paths
---
src/arcadepy/_utils/_transform.py | 14 +++++++++++++-
src/arcadepy/_utils/_typing.py | 2 ++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/arcadepy/_utils/_transform.py b/src/arcadepy/_utils/_transform.py
index 3b2b8e00..b0cc20a7 100644
--- a/src/arcadepy/_utils/_transform.py
+++ b/src/arcadepy/_utils/_transform.py
@@ -5,7 +5,7 @@
import pathlib
from typing import Any, Mapping, TypeVar, cast
from datetime import date, datetime
-from typing_extensions import Literal, get_args, override, get_type_hints
+from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
import anyio
import pydantic
@@ -13,6 +13,7 @@
from ._utils import (
is_list,
is_given,
+ lru_cache,
is_mapping,
is_iterable,
)
@@ -109,6 +110,7 @@ class Params(TypedDict, total=False):
return cast(_T, transformed)
+@lru_cache(maxsize=8096)
def _get_annotated_type(type_: type) -> type | None:
"""If the given type is an `Annotated` type then it is returned, if not `None` is returned.
@@ -433,3 +435,13 @@ async def _async_transform_typeddict(
else:
result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
return result
+
+
+@lru_cache(maxsize=8096)
+def get_type_hints(
+ obj: Any,
+ globalns: dict[str, Any] | None = None,
+ localns: Mapping[str, Any] | None = None,
+ include_extras: bool = False,
+) -> dict[str, Any]:
+ return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
diff --git a/src/arcadepy/_utils/_typing.py b/src/arcadepy/_utils/_typing.py
index 278749b1..1958820f 100644
--- a/src/arcadepy/_utils/_typing.py
+++ b/src/arcadepy/_utils/_typing.py
@@ -13,6 +13,7 @@
get_origin,
)
+from ._utils import lru_cache
from .._types import InheritsGeneric
from .._compat import is_union as _is_union
@@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+@lru_cache(maxsize=8096)
def strip_annotated_type(typ: type) -> type:
if is_required_type(typ) or is_annotated_type(typ):
return strip_annotated_type(cast(type, get_args(typ)[0]))
From 950f294ea47c8aa6ac423b6ffd2cec37278a4b4b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:29:08 +0000
Subject: [PATCH 21/40] chore(internal): update pyright settings
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index d3cf097e..72f3d4ae 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -147,6 +147,7 @@ exclude = [
]
reportImplicitOverride = true
+reportOverlappingOverload = false
reportImportCycles = false
reportPrivateUsage = false
From 4e548b86970ec4af361c474223255a8c847c4bff Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:30:25 +0000
Subject: [PATCH 22/40] chore(client): minor internal fixes
---
src/arcadepy/_base_client.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/arcadepy/_base_client.py b/src/arcadepy/_base_client.py
index d58046d3..9307ce08 100644
--- a/src/arcadepy/_base_client.py
+++ b/src/arcadepy/_base_client.py
@@ -409,7 +409,8 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
idempotency_header = self._idempotency_header
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
- headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
+ options.idempotency_key = options.idempotency_key or self._idempotency_key()
+ headers[idempotency_header] = options.idempotency_key
# Don't set these headers if they were already set or removed by the caller. We check
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
@@ -943,6 +944,10 @@ def _request(
request = self._build_request(options, retries_taken=retries_taken)
self._prepare_request(request)
+ if options.idempotency_key:
+ # ensure the idempotency key is reused between requests
+ input_options.idempotency_key = options.idempotency_key
+
kwargs: HttpxSendArgs = {}
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth
@@ -1475,6 +1480,10 @@ async def _request(
request = self._build_request(options, retries_taken=retries_taken)
await self._prepare_request(request)
+ if options.idempotency_key:
+ # ensure the idempotency key is reused between requests
+ input_options.idempotency_key = options.idempotency_key
+
kwargs: HttpxSendArgs = {}
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth
From 7290dc846a4b31d4eb5836c8d997561cacb9f2dd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 16 Apr 2025 17:32:27 +0000
Subject: [PATCH 23/40] feat(api): api update
---
.stats.yml | 4 ++--
src/arcadepy/resources/tools/tools.py | 4 ++--
src/arcadepy/types/tool_definition.py | 2 ++
src/arcadepy/types/tool_execute_params.py | 3 ++-
4 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 3921d93f..d3149136 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-992c4cfe194f4e4bfed0877e15c601e0577b661c779bb1b28d7d0531bba03677.yml
-openapi_spec_hash: 2a2799549615cc0b9b6d5dcc83441d15
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-39d3c370ae1640caf799b8b36a913a1fe09732ea7d075a88307bbedc1608dbe0.yml
+openapi_spec_hash: 76da95eb5887fef65da630799b4fd6ec
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/src/arcadepy/resources/tools/tools.py b/src/arcadepy/resources/tools/tools.py
index 4ce0a37f..18a44646 100644
--- a/src/arcadepy/resources/tools/tools.py
+++ b/src/arcadepy/resources/tools/tools.py
@@ -192,7 +192,7 @@ def execute(
input: JSON input to the tool, if any
run_at: The time at which the tool should be run (optional). If not provided, the tool
- is run immediately
+ is run immediately. Format ISO 8601: YYYY-MM-DDTHH:MM:SS
tool_version: The tool version to use (optional). If not provided, any version is used
@@ -403,7 +403,7 @@ async def execute(
input: JSON input to the tool, if any
run_at: The time at which the tool should be run (optional). If not provided, the tool
- is run immediately
+ is run immediately. Format ISO 8601: YYYY-MM-DDTHH:MM:SS
tool_version: The tool version to use (optional). If not provided, any version is used
diff --git a/src/arcadepy/types/tool_definition.py b/src/arcadepy/types/tool_definition.py
index 71a7ea62..cd7b2dee 100644
--- a/src/arcadepy/types/tool_definition.py
+++ b/src/arcadepy/types/tool_definition.py
@@ -87,4 +87,6 @@ class ToolDefinition(BaseModel):
output: Optional[Output] = None
+ qualified_name: Optional[str] = None
+
requirements: Optional[Requirements] = None
diff --git a/src/arcadepy/types/tool_execute_params.py b/src/arcadepy/types/tool_execute_params.py
index a3692a7b..8c354997 100644
--- a/src/arcadepy/types/tool_execute_params.py
+++ b/src/arcadepy/types/tool_execute_params.py
@@ -17,7 +17,8 @@ class ToolExecuteParams(TypedDict, total=False):
run_at: str
"""The time at which the tool should be run (optional).
- If not provided, the tool is run immediately
+ If not provided, the tool is run immediately. Format ISO 8601:
+ YYYY-MM-DDTHH:MM:SS
"""
tool_version: str
From 3133b01155fe75e7cfd14315ff8ab8fbabfe4ab1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 17 Apr 2025 00:35:41 +0000
Subject: [PATCH 24/40] feat(api): api update
---
.stats.yml | 4 +--
api.md | 2 +-
src/arcadepy/resources/tools/tools.py | 31 ++++++++++++++++++++---
src/arcadepy/types/__init__.py | 1 +
src/arcadepy/types/tool_definition.py | 4 ++-
src/arcadepy/types/tool_get_params.py | 13 ++++++++++
src/arcadepy/types/tool_list_params.py | 6 ++++-
tests/api_resources/test_tools.py | 34 ++++++++++++++++++++------
8 files changed, 78 insertions(+), 17 deletions(-)
create mode 100644 src/arcadepy/types/tool_get_params.py
diff --git a/.stats.yml b/.stats.yml
index d3149136..3539e93f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-39d3c370ae1640caf799b8b36a913a1fe09732ea7d075a88307bbedc1608dbe0.yml
-openapi_spec_hash: 76da95eb5887fef65da630799b4fd6ec
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-e2c2843c6c76fbfac81e81fd6dbe31f139049ad42f8e5036c4b681ee1db583f9.yml
+openapi_spec_hash: 76003263f277c3111a2a1666f80aca0d
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/api.md b/api.md
index 0f79d040..0baadae2 100644
--- a/api.md
+++ b/api.md
@@ -64,7 +64,7 @@ Methods:
- client.tools.list(\*\*params) -> SyncOffsetPage[ToolDefinition]
- client.tools.authorize(\*\*params) -> AuthorizationResponse
- client.tools.execute(\*\*params) -> ExecuteToolResponse
-- client.tools.get(name) -> ToolDefinition
+- client.tools.get(name, \*\*params) -> ToolDefinition
## Scheduled
diff --git a/src/arcadepy/resources/tools/tools.py b/src/arcadepy/resources/tools/tools.py
index 18a44646..351464fd 100644
--- a/src/arcadepy/resources/tools/tools.py
+++ b/src/arcadepy/resources/tools/tools.py
@@ -2,11 +2,12 @@
from __future__ import annotations
-from typing import Dict
+from typing import Dict, List
+from typing_extensions import Literal
import httpx
-from ...types import tool_list_params, tool_execute_params, tool_authorize_params
+from ...types import tool_get_params, tool_list_params, tool_execute_params, tool_authorize_params
from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
from ..._utils import (
maybe_transform,
@@ -76,6 +77,7 @@ def with_streaming_response(self) -> ToolsResourceWithStreamingResponse:
def list(
self,
*,
+ include_format: List[Literal["arcade", "openai", "anthropic"]] | NotGiven = NOT_GIVEN,
limit: int | NotGiven = NOT_GIVEN,
offset: int | NotGiven = NOT_GIVEN,
toolkit: str | NotGiven = NOT_GIVEN,
@@ -91,6 +93,8 @@ def list(
toolkit
Args:
+ include_format: Comma separated tool formats that will be included in the response.
+
limit: Number of items to return (default: 25, max: 100)
offset: Offset from the start of the list (default: 0)
@@ -115,6 +119,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "include_format": include_format,
"limit": limit,
"offset": offset,
"toolkit": toolkit,
@@ -226,6 +231,7 @@ def get(
self,
name: str,
*,
+ include_format: List[Literal["arcade", "openai", "anthropic"]] | NotGiven = NOT_GIVEN,
# 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,
@@ -237,6 +243,8 @@ def get(
Returns the arcade tool specification for a specific tool
Args:
+ include_format: Comma separated tool formats that will be included in the response.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -250,7 +258,11 @@ def get(
return self._get(
f"/v1/tools/{name}",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"include_format": include_format}, tool_get_params.ToolGetParams),
),
cast_to=ToolDefinition,
)
@@ -287,6 +299,7 @@ def with_streaming_response(self) -> AsyncToolsResourceWithStreamingResponse:
def list(
self,
*,
+ include_format: List[Literal["arcade", "openai", "anthropic"]] | NotGiven = NOT_GIVEN,
limit: int | NotGiven = NOT_GIVEN,
offset: int | NotGiven = NOT_GIVEN,
toolkit: str | NotGiven = NOT_GIVEN,
@@ -302,6 +315,8 @@ def list(
toolkit
Args:
+ include_format: Comma separated tool formats that will be included in the response.
+
limit: Number of items to return (default: 25, max: 100)
offset: Offset from the start of the list (default: 0)
@@ -326,6 +341,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "include_format": include_format,
"limit": limit,
"offset": offset,
"toolkit": toolkit,
@@ -437,6 +453,7 @@ async def get(
self,
name: str,
*,
+ include_format: List[Literal["arcade", "openai", "anthropic"]] | NotGiven = NOT_GIVEN,
# 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,
@@ -448,6 +465,8 @@ async def get(
Returns the arcade tool specification for a specific tool
Args:
+ include_format: Comma separated tool formats that will be included in the response.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -461,7 +480,11 @@ async def get(
return await self._get(
f"/v1/tools/{name}",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"include_format": include_format}, tool_get_params.ToolGetParams),
),
cast_to=ToolDefinition,
)
diff --git a/src/arcadepy/types/__init__.py b/src/arcadepy/types/__init__.py
index 1b6a4beb..6fd782f2 100644
--- a/src/arcadepy/types/__init__.py
+++ b/src/arcadepy/types/__init__.py
@@ -15,6 +15,7 @@
from .health_schema import HealthSchema as HealthSchema
from .tool_execution import ToolExecution as ToolExecution
from .tool_definition import ToolDefinition as ToolDefinition
+from .tool_get_params import ToolGetParams as ToolGetParams
from .worker_response import WorkerResponse as WorkerResponse
from .tool_list_params import ToolListParams as ToolListParams
from .tool_get_response import ToolGetResponse as ToolGetResponse
diff --git a/src/arcadepy/types/tool_definition.py b/src/arcadepy/types/tool_definition.py
index cd7b2dee..a68df5fb 100644
--- a/src/arcadepy/types/tool_definition.py
+++ b/src/arcadepy/types/tool_definition.py
@@ -1,6 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import List, Optional
+from typing import Dict, List, Optional
from .._models import BaseModel
from .value_schema import ValueSchema
@@ -83,6 +83,8 @@ class ToolDefinition(BaseModel):
description: Optional[str] = None
+ formatted_schema: Optional[Dict[str, object]] = None
+
fully_qualified_name: Optional[str] = None
output: Optional[Output] = None
diff --git a/src/arcadepy/types/tool_get_params.py b/src/arcadepy/types/tool_get_params.py
new file mode 100644
index 00000000..b3b25ae4
--- /dev/null
+++ b/src/arcadepy/types/tool_get_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 import List
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["ToolGetParams"]
+
+
+class ToolGetParams(TypedDict, total=False):
+ include_format: List[Literal["arcade", "openai", "anthropic"]]
+ """Comma separated tool formats that will be included in the response."""
diff --git a/src/arcadepy/types/tool_list_params.py b/src/arcadepy/types/tool_list_params.py
index 336d56a3..e2428ed3 100644
--- a/src/arcadepy/types/tool_list_params.py
+++ b/src/arcadepy/types/tool_list_params.py
@@ -2,12 +2,16 @@
from __future__ import annotations
-from typing_extensions import TypedDict
+from typing import List
+from typing_extensions import Literal, TypedDict
__all__ = ["ToolListParams"]
class ToolListParams(TypedDict, total=False):
+ include_format: List[Literal["arcade", "openai", "anthropic"]]
+ """Comma separated tool formats that will be included in the response."""
+
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 3727f7c6..6fd1456b 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_format=["arcade"],
limit=0,
offset=0,
toolkit="toolkit",
@@ -141,14 +142,22 @@ def test_streaming_response_execute(self, client: Arcade) -> None:
@parametrize
def test_method_get(self, client: Arcade) -> None:
tool = client.tools.get(
- "name",
+ name="name",
+ )
+ assert_matches_type(ToolDefinition, tool, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Arcade) -> None:
+ tool = client.tools.get(
+ name="name",
+ include_format=["arcade"],
)
assert_matches_type(ToolDefinition, tool, path=["response"])
@parametrize
def test_raw_response_get(self, client: Arcade) -> None:
response = client.tools.with_raw_response.get(
- "name",
+ name="name",
)
assert response.is_closed is True
@@ -159,7 +168,7 @@ def test_raw_response_get(self, client: Arcade) -> None:
@parametrize
def test_streaming_response_get(self, client: Arcade) -> None:
with client.tools.with_streaming_response.get(
- "name",
+ name="name",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -173,7 +182,7 @@ def test_streaming_response_get(self, client: Arcade) -> None:
def test_path_params_get(self, client: Arcade) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
client.tools.with_raw_response.get(
- "",
+ name="",
)
@@ -188,6 +197,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_format=["arcade"],
limit=0,
offset=0,
toolkit="toolkit",
@@ -299,14 +309,22 @@ async def test_streaming_response_execute(self, async_client: AsyncArcade) -> No
@parametrize
async def test_method_get(self, async_client: AsyncArcade) -> None:
tool = await async_client.tools.get(
- "name",
+ name="name",
+ )
+ assert_matches_type(ToolDefinition, tool, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncArcade) -> None:
+ tool = await async_client.tools.get(
+ name="name",
+ include_format=["arcade"],
)
assert_matches_type(ToolDefinition, tool, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncArcade) -> None:
response = await async_client.tools.with_raw_response.get(
- "name",
+ name="name",
)
assert response.is_closed is True
@@ -317,7 +335,7 @@ async def test_raw_response_get(self, async_client: AsyncArcade) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncArcade) -> None:
async with async_client.tools.with_streaming_response.get(
- "name",
+ name="name",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -331,5 +349,5 @@ async def test_streaming_response_get(self, async_client: AsyncArcade) -> None:
async def test_path_params_get(self, async_client: AsyncArcade) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"):
await async_client.tools.with_raw_response.get(
- "",
+ name="",
)
From 6681c14818b989c4968408fe7432414c326d9038 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 17 Apr 2025 02:26:49 +0000
Subject: [PATCH 25/40] chore(internal): bump pyright version
---
pyproject.toml | 2 +-
requirements-dev.lock | 2 +-
src/arcadepy/_base_client.py | 6 +++++-
src/arcadepy/_models.py | 1 -
src/arcadepy/_utils/_typing.py | 2 +-
tests/conftest.py | 2 +-
tests/test_models.py | 2 +-
7 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 72f3d4ae..2e6932da 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,7 +42,7 @@ Repository = "https://github.com/ArcadeAI/arcade-py"
managed = true
# version pins are in requirements-dev.lock
dev-dependencies = [
- "pyright>=1.1.359",
+ "pyright==1.1.399",
"mypy",
"respx",
"pytest",
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 75552309..340940c6 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -69,7 +69,7 @@ pydantic-core==2.27.1
# via pydantic
pygments==2.18.0
# via rich
-pyright==1.1.392.post0
+pyright==1.1.399
pytest==8.3.3
# via pytest-asyncio
pytest-asyncio==0.24.0
diff --git a/src/arcadepy/_base_client.py b/src/arcadepy/_base_client.py
index 9307ce08..9d2a7cb8 100644
--- a/src/arcadepy/_base_client.py
+++ b/src/arcadepy/_base_client.py
@@ -98,7 +98,11 @@
_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
if TYPE_CHECKING:
- from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
+ from httpx._config import (
+ DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage]
+ )
+
+ HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
else:
try:
from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index 34935716..58b9263e 100644
--- a/src/arcadepy/_models.py
+++ b/src/arcadepy/_models.py
@@ -19,7 +19,6 @@
)
import pydantic
-import pydantic.generics
from pydantic.fields import FieldInfo
from ._types import (
diff --git a/src/arcadepy/_utils/_typing.py b/src/arcadepy/_utils/_typing.py
index 1958820f..1bac9542 100644
--- a/src/arcadepy/_utils/_typing.py
+++ b/src/arcadepy/_utils/_typing.py
@@ -110,7 +110,7 @@ class MyResponse(Foo[_T]):
```
"""
cls = cast(object, get_origin(typ) or typ)
- if cls in generic_bases:
+ if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains]
# we're given the class directly
return extract_type_arg(typ, index)
diff --git a/tests/conftest.py b/tests/conftest.py
index 2345a0b2..5dbc7840 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,7 +10,7 @@
from arcadepy import Arcade, AsyncArcade
if TYPE_CHECKING:
- from _pytest.fixtures import FixtureRequest
+ from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
pytest.register_assert_rewrite("tests.utils")
diff --git a/tests/test_models.py b/tests/test_models.py
index da2f1234..1f79142e 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -832,7 +832,7 @@ class B(BaseModel):
@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1")
def test_type_alias_type() -> None:
- Alias = TypeAliasType("Alias", str)
+ Alias = TypeAliasType("Alias", str) # pyright: ignore
class Model(BaseModel):
alias: Alias
From 1a4a717957f74170069bfff0a415ddc38e66c5ef Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 17 Apr 2025 02:27:19 +0000
Subject: [PATCH 26/40] chore(internal): base client updates
---
src/arcadepy/_base_client.py | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/arcadepy/_base_client.py b/src/arcadepy/_base_client.py
index 9d2a7cb8..0563a6b9 100644
--- a/src/arcadepy/_base_client.py
+++ b/src/arcadepy/_base_client.py
@@ -119,6 +119,7 @@ class PageInfo:
url: URL | NotGiven
params: Query | NotGiven
+ json: Body | NotGiven
@overload
def __init__(
@@ -134,19 +135,30 @@ def __init__(
params: Query,
) -> None: ...
+ @overload
+ def __init__(
+ self,
+ *,
+ json: Body,
+ ) -> None: ...
+
def __init__(
self,
*,
url: URL | NotGiven = NOT_GIVEN,
+ json: Body | NotGiven = NOT_GIVEN,
params: Query | NotGiven = NOT_GIVEN,
) -> None:
self.url = url
+ self.json = json
self.params = params
@override
def __repr__(self) -> str:
if self.url:
return f"{self.__class__.__name__}(url={self.url})"
+ if self.json:
+ return f"{self.__class__.__name__}(json={self.json})"
return f"{self.__class__.__name__}(params={self.params})"
@@ -195,6 +207,19 @@ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
options.url = str(url)
return options
+ if not isinstance(info.json, NotGiven):
+ if not is_mapping(info.json):
+ raise TypeError("Pagination is only supported with mappings")
+
+ if not options.json_data:
+ options.json_data = {**info.json}
+ else:
+ if not is_mapping(options.json_data):
+ raise TypeError("Pagination is only supported with mappings")
+
+ options.json_data = {**options.json_data, **info.json}
+ return options
+
raise ValueError("Unexpected PageInfo state")
From ced349577a3c0c5b033ac53ebd239f333bc13fdf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 19 Apr 2025 02:30:32 +0000
Subject: [PATCH 27/40] chore(internal): update models test
---
tests/test_models.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/test_models.py b/tests/test_models.py
index 1f79142e..d96a3763 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -492,12 +492,15 @@ class Model(BaseModel):
resource_id: Optional[str] = None
m = Model.construct()
+ assert m.resource_id is None
assert "resource_id" not in m.model_fields_set
m = Model.construct(resource_id=None)
+ assert m.resource_id is None
assert "resource_id" in m.model_fields_set
m = Model.construct(resource_id="foo")
+ assert m.resource_id == "foo"
assert "resource_id" in m.model_fields_set
From fb62a6583c4c3af75eb3789e80d79f0306a5c7ec Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 21 Apr 2025 16:12:15 +0000
Subject: [PATCH 28/40] feat(api): api update
---
.stats.yml | 4 ++--
src/arcadepy/types/worker_response.py | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 3539e93f..9fd689b9 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-e2c2843c6c76fbfac81e81fd6dbe31f139049ad42f8e5036c4b681ee1db583f9.yml
-openapi_spec_hash: 76003263f277c3111a2a1666f80aca0d
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-86691824f99db266d91df27620f32b992216babea405980db172d7602dc6946d.yml
+openapi_spec_hash: 23902db657bd5f5f22bc6777f590cb66
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/src/arcadepy/types/worker_response.py b/src/arcadepy/types/worker_response.py
index 85c899bc..01276285 100644
--- a/src/arcadepy/types/worker_response.py
+++ b/src/arcadepy/types/worker_response.py
@@ -11,11 +11,11 @@
class Binding(BaseModel):
id: Optional[str] = None
- type: Optional[Literal["static", "tenant", "organization", "account"]] = None
+ type: Optional[Literal["static", "tenant", "project", "account"]] = None
class HTTPSecret(BaseModel):
- binding: Optional[Literal["static", "tenant", "organization", "account"]] = None
+ binding: Optional[Literal["static", "tenant", "project", "account"]] = None
editable: Optional[bool] = None
@@ -45,7 +45,7 @@ class Mcp(BaseModel):
class OxpSecret(BaseModel):
- binding: Optional[Literal["static", "tenant", "organization", "account"]] = None
+ binding: Optional[Literal["static", "tenant", "project", "account"]] = None
editable: Optional[bool] = None
From 467dfb2ae0da1a048b9055630efa9a40126060dd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:54:47 +0000
Subject: [PATCH 29/40] chore(ci): add timeout thresholds for CI jobs
---
.github/workflows/ci.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 81f6dc20..04b083ca 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,6 +10,7 @@ on:
jobs:
lint:
+ timeout-minutes: 10
name: lint
runs-on: ubuntu-latest
steps:
@@ -30,6 +31,7 @@ jobs:
run: ./scripts/lint
test:
+ timeout-minutes: 10
name: test
runs-on: ubuntu-latest
steps:
From 772a87cd938e2d0f008cc54f3843ca4dad567225 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:55:15 +0000
Subject: [PATCH 30/40] chore(internal): import reformatting
---
src/arcadepy/_client.py | 5 +----
src/arcadepy/resources/auth.py | 5 +----
src/arcadepy/resources/chat/completions.py | 5 +----
src/arcadepy/resources/tools/formatted.py | 5 +----
src/arcadepy/resources/tools/tools.py | 5 +----
src/arcadepy/resources/workers.py | 5 +----
6 files changed, 6 insertions(+), 24 deletions(-)
diff --git a/src/arcadepy/_client.py b/src/arcadepy/_client.py
index 1cd8c83f..e8e9e51d 100644
--- a/src/arcadepy/_client.py
+++ b/src/arcadepy/_client.py
@@ -19,10 +19,7 @@
ProxiesTypes,
RequestOptions,
)
-from ._utils import (
- is_given,
- get_async_library,
-)
+from ._utils import is_given, get_async_library
from ._version import __version__
from .resources import auth, health, workers
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
diff --git a/src/arcadepy/resources/auth.py b/src/arcadepy/resources/auth.py
index 2a8c256f..115632c2 100644
--- a/src/arcadepy/resources/auth.py
+++ b/src/arcadepy/resources/auth.py
@@ -6,10 +6,7 @@
from ..types import auth_status_params, auth_authorize_params
from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from .._utils import (
- maybe_transform,
- async_maybe_transform,
-)
+from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
diff --git a/src/arcadepy/resources/chat/completions.py b/src/arcadepy/resources/chat/completions.py
index d2703c64..d7577e97 100644
--- a/src/arcadepy/resources/chat/completions.py
+++ b/src/arcadepy/resources/chat/completions.py
@@ -7,10 +7,7 @@
import httpx
from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from ..._utils import (
- maybe_transform,
- async_maybe_transform,
-)
+from ..._utils import maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
diff --git a/src/arcadepy/resources/tools/formatted.py b/src/arcadepy/resources/tools/formatted.py
index e4d87fc6..ebd8a1ad 100644
--- a/src/arcadepy/resources/tools/formatted.py
+++ b/src/arcadepy/resources/tools/formatted.py
@@ -5,10 +5,7 @@
import httpx
from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from ..._utils import (
- maybe_transform,
- async_maybe_transform,
-)
+from ..._utils import maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
diff --git a/src/arcadepy/resources/tools/tools.py b/src/arcadepy/resources/tools/tools.py
index 351464fd..78b47bbc 100644
--- a/src/arcadepy/resources/tools/tools.py
+++ b/src/arcadepy/resources/tools/tools.py
@@ -9,10 +9,7 @@
from ...types import tool_get_params, tool_list_params, tool_execute_params, tool_authorize_params
from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven
-from ..._utils import (
- maybe_transform,
- async_maybe_transform,
-)
+from ..._utils import maybe_transform, async_maybe_transform
from ..._compat import cached_property
from .formatted import (
FormattedResource,
diff --git a/src/arcadepy/resources/workers.py b/src/arcadepy/resources/workers.py
index 78904108..3fa6e2c6 100644
--- a/src/arcadepy/resources/workers.py
+++ b/src/arcadepy/resources/workers.py
@@ -6,10 +6,7 @@
from ..types import worker_list_params, worker_tools_params, worker_create_params, worker_update_params
from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
-from .._utils import (
- maybe_transform,
- async_maybe_transform,
-)
+from .._utils import maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
From 1f6d1f33d2323d2d0dec2ccfa9e21cf0b208bdd1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:56:33 +0000
Subject: [PATCH 31/40] chore(internal): fix list file params
---
src/arcadepy/_utils/_utils.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/arcadepy/_utils/_utils.py b/src/arcadepy/_utils/_utils.py
index e5811bba..ea3cf3f2 100644
--- a/src/arcadepy/_utils/_utils.py
+++ b/src/arcadepy/_utils/_utils.py
@@ -72,8 +72,16 @@ def _extract_items(
from .._files import assert_is_file_content
# We have exhausted the path, return the entry we found.
- assert_is_file_content(obj, key=flattened_key)
assert flattened_key is not None
+
+ if is_list(obj):
+ files: list[tuple[str, FileTypes]] = []
+ for entry in obj:
+ assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
+ files.append((flattened_key + "[]", cast(FileTypes, entry)))
+ return files
+
+ assert_is_file_content(obj, key=flattened_key)
return [(flattened_key, cast(FileTypes, obj))]
index += 1
From 709debf17814c59c6048b996195090ee677119ed Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:57:04 +0000
Subject: [PATCH 32/40] chore(internal): refactor retries to not use recursion
---
src/arcadepy/_base_client.py | 414 +++++++++++++++--------------------
1 file changed, 175 insertions(+), 239 deletions(-)
diff --git a/src/arcadepy/_base_client.py b/src/arcadepy/_base_client.py
index 0563a6b9..4e204d4d 100644
--- a/src/arcadepy/_base_client.py
+++ b/src/arcadepy/_base_client.py
@@ -437,8 +437,7 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
headers = httpx.Headers(headers_dict)
idempotency_header = self._idempotency_header
- if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
- options.idempotency_key = options.idempotency_key or self._idempotency_key()
+ if idempotency_header and options.idempotency_key and idempotency_header not in headers:
headers[idempotency_header] = options.idempotency_key
# Don't set these headers if they were already set or removed by the caller. We check
@@ -903,7 +902,6 @@ def request(
self,
cast_to: Type[ResponseT],
options: FinalRequestOptions,
- remaining_retries: Optional[int] = None,
*,
stream: Literal[True],
stream_cls: Type[_StreamT],
@@ -914,7 +912,6 @@ def request(
self,
cast_to: Type[ResponseT],
options: FinalRequestOptions,
- remaining_retries: Optional[int] = None,
*,
stream: Literal[False] = False,
) -> ResponseT: ...
@@ -924,7 +921,6 @@ def request(
self,
cast_to: Type[ResponseT],
options: FinalRequestOptions,
- remaining_retries: Optional[int] = None,
*,
stream: bool = False,
stream_cls: Type[_StreamT] | None = None,
@@ -934,125 +930,109 @@ def request(
self,
cast_to: Type[ResponseT],
options: FinalRequestOptions,
- remaining_retries: Optional[int] = None,
*,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
- if remaining_retries is not None:
- retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
- else:
- retries_taken = 0
-
- return self._request(
- cast_to=cast_to,
- options=options,
- stream=stream,
- stream_cls=stream_cls,
- retries_taken=retries_taken,
- )
+ cast_to = self._maybe_override_cast_to(cast_to, options)
- def _request(
- self,
- *,
- cast_to: Type[ResponseT],
- options: FinalRequestOptions,
- retries_taken: int,
- stream: bool,
- stream_cls: type[_StreamT] | None,
- ) -> ResponseT | _StreamT:
# create a copy of the options we were given so that if the
# options are mutated later & we then retry, the retries are
# given the original options
input_options = model_copy(options)
-
- cast_to = self._maybe_override_cast_to(cast_to, options)
- options = self._prepare_options(options)
-
- remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
- request = self._build_request(options, retries_taken=retries_taken)
- self._prepare_request(request)
-
- if options.idempotency_key:
+ if input_options.idempotency_key is None and input_options.method.lower() != "get":
# ensure the idempotency key is reused between requests
- input_options.idempotency_key = options.idempotency_key
+ input_options.idempotency_key = self._idempotency_key()
- kwargs: HttpxSendArgs = {}
- if self.custom_auth is not None:
- kwargs["auth"] = self.custom_auth
+ response: httpx.Response | None = None
+ max_retries = input_options.get_max_retries(self.max_retries)
- log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+ retries_taken = 0
+ for retries_taken in range(max_retries + 1):
+ options = model_copy(input_options)
+ options = self._prepare_options(options)
- try:
- response = self._client.send(
- request,
- stream=stream or self._should_stream_response_body(request=request),
- **kwargs,
- )
- except httpx.TimeoutException as err:
- log.debug("Encountered httpx.TimeoutException", exc_info=True)
+ remaining_retries = max_retries - retries_taken
+ request = self._build_request(options, retries_taken=retries_taken)
+ self._prepare_request(request)
- if remaining_retries > 0:
- return self._retry_request(
- input_options,
- cast_to,
- retries_taken=retries_taken,
- stream=stream,
- stream_cls=stream_cls,
- response_headers=None,
- )
+ kwargs: HttpxSendArgs = {}
+ if self.custom_auth is not None:
+ kwargs["auth"] = self.custom_auth
- log.debug("Raising timeout error")
- raise APITimeoutError(request=request) from err
- except Exception as err:
- log.debug("Encountered Exception", exc_info=True)
+ log.debug("Sending HTTP Request: %s %s", request.method, request.url)
- if remaining_retries > 0:
- return self._retry_request(
- input_options,
- cast_to,
- retries_taken=retries_taken,
- stream=stream,
- stream_cls=stream_cls,
- response_headers=None,
+ response = None
+ try:
+ response = self._client.send(
+ request,
+ stream=stream or self._should_stream_response_body(request=request),
+ **kwargs,
)
+ except httpx.TimeoutException as err:
+ log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+ if remaining_retries > 0:
+ self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
+
+ log.debug("Raising timeout error")
+ raise APITimeoutError(request=request) from err
+ except Exception as err:
+ log.debug("Encountered Exception", exc_info=True)
+
+ if remaining_retries > 0:
+ self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
+
+ log.debug("Raising connection error")
+ raise APIConnectionError(request=request) from err
+
+ log.debug(
+ 'HTTP Response: %s %s "%i %s" %s',
+ request.method,
+ request.url,
+ response.status_code,
+ response.reason_phrase,
+ response.headers,
+ )
- log.debug("Raising connection error")
- raise APIConnectionError(request=request) from err
-
- log.debug(
- 'HTTP Response: %s %s "%i %s" %s',
- request.method,
- request.url,
- response.status_code,
- response.reason_phrase,
- response.headers,
- )
+ try:
+ response.raise_for_status()
+ except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
+ log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+ if remaining_retries > 0 and self._should_retry(err.response):
+ err.response.close()
+ self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=response,
+ )
+ continue
- try:
- response.raise_for_status()
- except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
- log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
-
- if remaining_retries > 0 and self._should_retry(err.response):
- err.response.close()
- return self._retry_request(
- input_options,
- cast_to,
- retries_taken=retries_taken,
- response_headers=err.response.headers,
- stream=stream,
- stream_cls=stream_cls,
- )
+ # If the response is streamed then we need to explicitly read the response
+ # to completion before attempting to access the response text.
+ if not err.response.is_closed:
+ err.response.read()
- # If the response is streamed then we need to explicitly read the response
- # to completion before attempting to access the response text.
- if not err.response.is_closed:
- err.response.read()
+ log.debug("Re-raising status error")
+ raise self._make_status_error_from_response(err.response) from None
- log.debug("Re-raising status error")
- raise self._make_status_error_from_response(err.response) from None
+ break
+ assert response is not None, "could not resolve response (should never happen)"
return self._process_response(
cast_to=cast_to,
options=options,
@@ -1062,37 +1042,20 @@ def _request(
retries_taken=retries_taken,
)
- def _retry_request(
- self,
- options: FinalRequestOptions,
- cast_to: Type[ResponseT],
- *,
- retries_taken: int,
- response_headers: httpx.Headers | None,
- stream: bool,
- stream_cls: type[_StreamT] | None,
- ) -> ResponseT | _StreamT:
- remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+ def _sleep_for_retry(
+ self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+ ) -> None:
+ remaining_retries = max_retries - retries_taken
if remaining_retries == 1:
log.debug("1 retry left")
else:
log.debug("%i retries left", remaining_retries)
- timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
+ timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
log.info("Retrying request to %s in %f seconds", options.url, timeout)
- # In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a
- # different thread if necessary.
time.sleep(timeout)
- return self._request(
- options=options,
- cast_to=cast_to,
- retries_taken=retries_taken + 1,
- stream=stream,
- stream_cls=stream_cls,
- )
-
def _process_response(
self,
*,
@@ -1436,7 +1399,6 @@ async def request(
options: FinalRequestOptions,
*,
stream: Literal[False] = False,
- remaining_retries: Optional[int] = None,
) -> ResponseT: ...
@overload
@@ -1447,7 +1409,6 @@ async def request(
*,
stream: Literal[True],
stream_cls: type[_AsyncStreamT],
- remaining_retries: Optional[int] = None,
) -> _AsyncStreamT: ...
@overload
@@ -1458,7 +1419,6 @@ async def request(
*,
stream: bool,
stream_cls: type[_AsyncStreamT] | None = None,
- remaining_retries: Optional[int] = None,
) -> ResponseT | _AsyncStreamT: ...
async def request(
@@ -1468,120 +1428,111 @@ async def request(
*,
stream: bool = False,
stream_cls: type[_AsyncStreamT] | None = None,
- remaining_retries: Optional[int] = None,
- ) -> ResponseT | _AsyncStreamT:
- if remaining_retries is not None:
- retries_taken = options.get_max_retries(self.max_retries) - remaining_retries
- else:
- retries_taken = 0
-
- return await self._request(
- cast_to=cast_to,
- options=options,
- stream=stream,
- stream_cls=stream_cls,
- retries_taken=retries_taken,
- )
-
- async def _request(
- self,
- cast_to: Type[ResponseT],
- options: FinalRequestOptions,
- *,
- stream: bool,
- stream_cls: type[_AsyncStreamT] | None,
- retries_taken: int,
) -> ResponseT | _AsyncStreamT:
if self._platform is None:
# `get_platform` can make blocking IO calls so we
# execute it earlier while we are in an async context
self._platform = await asyncify(get_platform)()
+ cast_to = self._maybe_override_cast_to(cast_to, options)
+
# create a copy of the options we were given so that if the
# options are mutated later & we then retry, the retries are
# given the original options
input_options = model_copy(options)
-
- cast_to = self._maybe_override_cast_to(cast_to, options)
- options = await self._prepare_options(options)
-
- remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
- request = self._build_request(options, retries_taken=retries_taken)
- await self._prepare_request(request)
-
- if options.idempotency_key:
+ if input_options.idempotency_key is None and input_options.method.lower() != "get":
# ensure the idempotency key is reused between requests
- input_options.idempotency_key = options.idempotency_key
+ input_options.idempotency_key = self._idempotency_key()
- kwargs: HttpxSendArgs = {}
- if self.custom_auth is not None:
- kwargs["auth"] = self.custom_auth
+ response: httpx.Response | None = None
+ max_retries = input_options.get_max_retries(self.max_retries)
- try:
- response = await self._client.send(
- request,
- stream=stream or self._should_stream_response_body(request=request),
- **kwargs,
- )
- except httpx.TimeoutException as err:
- log.debug("Encountered httpx.TimeoutException", exc_info=True)
+ retries_taken = 0
+ for retries_taken in range(max_retries + 1):
+ options = model_copy(input_options)
+ options = await self._prepare_options(options)
- if remaining_retries > 0:
- return await self._retry_request(
- input_options,
- cast_to,
- retries_taken=retries_taken,
- stream=stream,
- stream_cls=stream_cls,
- response_headers=None,
- )
+ remaining_retries = max_retries - retries_taken
+ request = self._build_request(options, retries_taken=retries_taken)
+ await self._prepare_request(request)
- log.debug("Raising timeout error")
- raise APITimeoutError(request=request) from err
- except Exception as err:
- log.debug("Encountered Exception", exc_info=True)
+ kwargs: HttpxSendArgs = {}
+ if self.custom_auth is not None:
+ kwargs["auth"] = self.custom_auth
- if remaining_retries > 0:
- return await self._retry_request(
- input_options,
- cast_to,
- retries_taken=retries_taken,
- stream=stream,
- stream_cls=stream_cls,
- response_headers=None,
- )
+ log.debug("Sending HTTP Request: %s %s", request.method, request.url)
- log.debug("Raising connection error")
- raise APIConnectionError(request=request) from err
+ response = None
+ try:
+ response = await self._client.send(
+ request,
+ stream=stream or self._should_stream_response_body(request=request),
+ **kwargs,
+ )
+ except httpx.TimeoutException as err:
+ log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+ if remaining_retries > 0:
+ await self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
+
+ log.debug("Raising timeout error")
+ raise APITimeoutError(request=request) from err
+ except Exception as err:
+ log.debug("Encountered Exception", exc_info=True)
+
+ if remaining_retries > 0:
+ await self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
+
+ log.debug("Raising connection error")
+ raise APIConnectionError(request=request) from err
+
+ log.debug(
+ 'HTTP Response: %s %s "%i %s" %s',
+ request.method,
+ request.url,
+ response.status_code,
+ response.reason_phrase,
+ response.headers,
+ )
- log.debug(
- 'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
- )
+ try:
+ response.raise_for_status()
+ except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
+ log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+ if remaining_retries > 0 and self._should_retry(err.response):
+ await err.response.aclose()
+ await self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=response,
+ )
+ continue
- try:
- response.raise_for_status()
- except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
- log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
-
- if remaining_retries > 0 and self._should_retry(err.response):
- await err.response.aclose()
- return await self._retry_request(
- input_options,
- cast_to,
- retries_taken=retries_taken,
- response_headers=err.response.headers,
- stream=stream,
- stream_cls=stream_cls,
- )
+ # If the response is streamed then we need to explicitly read the response
+ # to completion before attempting to access the response text.
+ if not err.response.is_closed:
+ await err.response.aread()
- # If the response is streamed then we need to explicitly read the response
- # to completion before attempting to access the response text.
- if not err.response.is_closed:
- await err.response.aread()
+ log.debug("Re-raising status error")
+ raise self._make_status_error_from_response(err.response) from None
- log.debug("Re-raising status error")
- raise self._make_status_error_from_response(err.response) from None
+ break
+ assert response is not None, "could not resolve response (should never happen)"
return await self._process_response(
cast_to=cast_to,
options=options,
@@ -1591,35 +1542,20 @@ async def _request(
retries_taken=retries_taken,
)
- async def _retry_request(
- self,
- options: FinalRequestOptions,
- cast_to: Type[ResponseT],
- *,
- retries_taken: int,
- response_headers: httpx.Headers | None,
- stream: bool,
- stream_cls: type[_AsyncStreamT] | None,
- ) -> ResponseT | _AsyncStreamT:
- remaining_retries = options.get_max_retries(self.max_retries) - retries_taken
+ async def _sleep_for_retry(
+ self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+ ) -> None:
+ remaining_retries = max_retries - retries_taken
if remaining_retries == 1:
log.debug("1 retry left")
else:
log.debug("%i retries left", remaining_retries)
- timeout = self._calculate_retry_timeout(remaining_retries, options, response_headers)
+ timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
log.info("Retrying request to %s in %f seconds", options.url, timeout)
await anyio.sleep(timeout)
- return await self._request(
- options=options,
- cast_to=cast_to,
- retries_taken=retries_taken + 1,
- stream=stream,
- stream_cls=stream_cls,
- )
-
async def _process_response(
self,
*,
From be8bb32f9fab2a893fd6b11649c4f48b77fbd79d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Apr 2025 02:57:32 +0000
Subject: [PATCH 33/40] fix(pydantic v1): more robust ModelField.annotation
check
---
src/arcadepy/_models.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/arcadepy/_models.py b/src/arcadepy/_models.py
index 58b9263e..798956f1 100644
--- a/src/arcadepy/_models.py
+++ b/src/arcadepy/_models.py
@@ -626,8 +626,8 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
# Note: if one variant defines an alias then they all should
discriminator_alias = field_info.alias
- if field_info.annotation and is_literal_type(field_info.annotation):
- for entry in get_args(field_info.annotation):
+ if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
+ for entry in get_args(annotation):
if isinstance(entry, str):
mapping[entry] = variant
From b551ba99820f93fdecb24663e83dff11897f8f62 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:23:36 +0000
Subject: [PATCH 34/40] chore(internal): codegen related update
---
.github/workflows/ci.yml | 16 ++++++++--------
.github/workflows/publish-pypi.yml | 2 +-
.github/workflows/release-doctor.yml | 2 +-
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 04b083ca..33820422 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,18 @@
name: CI
on:
push:
- branches:
- - main
- pull_request:
- branches:
- - main
- - next
+ branches-ignore:
+ - 'generated'
+ - 'codegen/**'
+ - 'integrated/**'
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ubuntu-latest
+ runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -33,7 +33,7 @@ jobs:
test:
timeout-minutes: 10
name: test
- runs-on: ubuntu-latest
+ runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index 0080ea4c..bd0819f9 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -11,7 +11,7 @@ on:
jobs:
publish:
name: publish
- runs-on: ubuntu-latest
+ runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index d4ed5cf1..63635f4b 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -8,7 +8,7 @@ on:
jobs:
release_doctor:
name: release doctor
- runs-on: ubuntu-latest
+ runs-on: depot-ubuntu-24.04
if: github.repository == 'ArcadeAI/arcade-py' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
From eb51e05c0d9095ddf0cda7b97984254b03b62be1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:24:06 +0000
Subject: [PATCH 35/40] chore(ci): only use depot for staging repos
---
.github/workflows/ci.yml | 4 ++--
.github/workflows/publish-pypi.yml | 2 +-
.github/workflows/release-doctor.yml | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 33820422..5b1a5598 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: depot-ubuntu-24.04
+ runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v4
@@ -33,7 +33,7 @@ jobs:
test:
timeout-minutes: 10
name: test
- runs-on: depot-ubuntu-24.04
+ runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
index bd0819f9..0080ea4c 100644
--- a/.github/workflows/publish-pypi.yml
+++ b/.github/workflows/publish-pypi.yml
@@ -11,7 +11,7 @@ on:
jobs:
publish:
name: publish
- runs-on: depot-ubuntu-24.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 63635f4b..d4ed5cf1 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -8,7 +8,7 @@ on:
jobs:
release_doctor:
name: release doctor
- runs-on: depot-ubuntu-24.04
+ runs-on: ubuntu-latest
if: github.repository == 'ArcadeAI/arcade-py' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
From b562715e721a8aa1b53414c989fd573602e2197a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Apr 2025 02:25:48 +0000
Subject: [PATCH 36/40] chore: broadly detect json family of content-type
headers
---
src/arcadepy/_response.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/arcadepy/_response.py b/src/arcadepy/_response.py
index cfc188e2..eb5ab8c2 100644
--- a/src/arcadepy/_response.py
+++ b/src/arcadepy/_response.py
@@ -233,7 +233,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
# split is required to handle cases where additional information is included
# in the response, e.g. application/json; charset=utf-8
content_type, *_ = response.headers.get("content-type", "*").split(";")
- if content_type != "application/json":
+ if not content_type.endswith("json"):
if is_basemodel(cast_to):
try:
data = response.json()
From db1894bccf5cfc6078133a0cbceeefc25f473532 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 17:03:56 +0000
Subject: [PATCH 37/40] feat(api): api update
---
.stats.yml | 4 ++--
src/arcadepy/types/tool_definition.py | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 9fd689b9..dd3dbf55 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-86691824f99db266d91df27620f32b992216babea405980db172d7602dc6946d.yml
-openapi_spec_hash: 23902db657bd5f5f22bc6777f590cb66
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-47cf16b4cbddd7b80f8616763adfa3b376c662a4585dfec79a9bb86ab23c77ff.yml
+openapi_spec_hash: da2b53715045a404ae687cdfd032d9a5
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
diff --git a/src/arcadepy/types/tool_definition.py b/src/arcadepy/types/tool_definition.py
index a68df5fb..1fdbc15c 100644
--- a/src/arcadepy/types/tool_definition.py
+++ b/src/arcadepy/types/tool_definition.py
@@ -75,20 +75,20 @@ class Requirements(BaseModel):
class ToolDefinition(BaseModel):
+ fully_qualified_name: str
+
input: Input
name: str
+ qualified_name: str
+
toolkit: Toolkit
description: Optional[str] = None
formatted_schema: Optional[Dict[str, object]] = None
- fully_qualified_name: Optional[str] = None
-
output: Optional[Output] = None
- qualified_name: Optional[str] = None
-
requirements: Optional[Requirements] = None
From 8ebf77068b377450788bd7436bd3ed264a195805 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 22:21:09 +0000
Subject: [PATCH 38/40] feat(api): api update
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index dd3dbf55..b53e0604 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 19
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-47cf16b4cbddd7b80f8616763adfa3b376c662a4585dfec79a9bb86ab23c77ff.yml
-openapi_spec_hash: da2b53715045a404ae687cdfd032d9a5
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-4ab7584c83c5c3a70e1dbe129614563b90a0cca67a62e77a394964af6d495187.yml
+openapi_spec_hash: 28328429c60e98b6713f717951688ba9
config_hash: d86655f9af7ae4c4c444d9a16685a7c5
From d0aee1c22f9a1f6b6da855b82b31e68da080b0eb Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 22:21:32 +0000
Subject: [PATCH 39/40] release: 1.4.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 54 +++++++++++++++++++++++++++++++++++
pyproject.toml | 2 +-
src/arcadepy/_version.py | 2 +-
4 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 0e5b256d..3e9af1b3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "1.3.1"
+ ".": "1.4.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7525cc77..e47270ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,59 @@
# Changelog
+## 1.4.0 (2025-04-25)
+
+Full Changelog: [v1.3.1...v1.4.0](https://github.com/ArcadeAI/arcade-py/compare/v1.3.1...v1.4.0)
+
+### Features
+
+* **api:** api update ([8ebf770](https://github.com/ArcadeAI/arcade-py/commit/8ebf77068b377450788bd7436bd3ed264a195805))
+* **api:** api update ([db1894b](https://github.com/ArcadeAI/arcade-py/commit/db1894bccf5cfc6078133a0cbceeefc25f473532))
+* **api:** api update ([fb62a65](https://github.com/ArcadeAI/arcade-py/commit/fb62a6583c4c3af75eb3789e80d79f0306a5c7ec))
+* **api:** api update ([3133b01](https://github.com/ArcadeAI/arcade-py/commit/3133b01155fe75e7cfd14315ff8ab8fbabfe4ab1))
+* **api:** api update ([7290dc8](https://github.com/ArcadeAI/arcade-py/commit/7290dc846a4b31d4eb5836c8d997561cacb9f2dd))
+* **api:** api update ([#137](https://github.com/ArcadeAI/arcade-py/issues/137)) ([7ce1f6a](https://github.com/ArcadeAI/arcade-py/commit/7ce1f6aee46e2d8763c194a88f28428df9eb6d5f))
+* **api:** api update ([#138](https://github.com/ArcadeAI/arcade-py/issues/138)) ([cb71d87](https://github.com/ArcadeAI/arcade-py/commit/cb71d87280889f8d1200be0d069a088560c1bc8b))
+* **api:** api update ([#139](https://github.com/ArcadeAI/arcade-py/issues/139)) ([9a0ef5a](https://github.com/ArcadeAI/arcade-py/commit/9a0ef5a37cd20950fde494f690dd403715029243))
+
+
+### Bug Fixes
+
+* **ci:** ensure pip is always available ([#135](https://github.com/ArcadeAI/arcade-py/issues/135)) ([fd63bd1](https://github.com/ArcadeAI/arcade-py/commit/fd63bd10897027f3a2ea9e82043943213c1f897f))
+* **ci:** remove publishing patch ([#136](https://github.com/ArcadeAI/arcade-py/issues/136)) ([bd4bfc8](https://github.com/ArcadeAI/arcade-py/commit/bd4bfc8d40a1a59ac65e862eb6f0e04e0184c75d))
+* **perf:** optimize some hot paths ([c544b05](https://github.com/ArcadeAI/arcade-py/commit/c544b05d5afac34fe9119b791add080567f0e4cf))
+* **perf:** skip traversing types for NotGiven values ([af97129](https://github.com/ArcadeAI/arcade-py/commit/af97129e7ce0dc09566e4166cbaaa2b6a2246864))
+* **pydantic v1:** more robust ModelField.annotation check ([be8bb32](https://github.com/ArcadeAI/arcade-py/commit/be8bb32f9fab2a893fd6b11649c4f48b77fbd79d))
+* **types:** handle more discriminated union shapes ([#134](https://github.com/ArcadeAI/arcade-py/issues/134)) ([2e8aa54](https://github.com/ArcadeAI/arcade-py/commit/2e8aa5469824223540f3c1f970662bf0ed5f62ac))
+
+
+### Chores
+
+* broadly detect json family of content-type headers ([b562715](https://github.com/ArcadeAI/arcade-py/commit/b562715e721a8aa1b53414c989fd573602e2197a))
+* **ci:** add timeout thresholds for CI jobs ([467dfb2](https://github.com/ArcadeAI/arcade-py/commit/467dfb2ae0da1a048b9055630efa9a40126060dd))
+* **ci:** only use depot for staging repos ([eb51e05](https://github.com/ArcadeAI/arcade-py/commit/eb51e05c0d9095ddf0cda7b97984254b03b62be1))
+* **client:** minor internal fixes ([4e548b8](https://github.com/ArcadeAI/arcade-py/commit/4e548b86970ec4af361c474223255a8c847c4bff))
+* **internal:** base client updates ([1a4a717](https://github.com/ArcadeAI/arcade-py/commit/1a4a717957f74170069bfff0a415ddc38e66c5ef))
+* **internal:** bump pyright version ([6681c14](https://github.com/ArcadeAI/arcade-py/commit/6681c14818b989c4968408fe7432414c326d9038))
+* **internal:** bump rye to 0.44.0 ([#133](https://github.com/ArcadeAI/arcade-py/issues/133)) ([0068d95](https://github.com/ArcadeAI/arcade-py/commit/0068d951be52d203c558d6a241feca26eb1f3613))
+* **internal:** codegen related update ([b551ba9](https://github.com/ArcadeAI/arcade-py/commit/b551ba99820f93fdecb24663e83dff11897f8f62))
+* **internal:** codegen related update ([#132](https://github.com/ArcadeAI/arcade-py/issues/132)) ([85ff426](https://github.com/ArcadeAI/arcade-py/commit/85ff426032303355d1d8c331813aaeeeaef0f69d))
+* **internal:** expand CI branch coverage ([02cc295](https://github.com/ArcadeAI/arcade-py/commit/02cc2952f5b3bc36645d1da528f46ac65b681fd5))
+* **internal:** fix list file params ([1f6d1f3](https://github.com/ArcadeAI/arcade-py/commit/1f6d1f33d2323d2d0dec2ccfa9e21cf0b208bdd1))
+* **internal:** import reformatting ([772a87c](https://github.com/ArcadeAI/arcade-py/commit/772a87cd938e2d0f008cc54f3843ca4dad567225))
+* **internal:** reduce CI branch coverage ([f6d1892](https://github.com/ArcadeAI/arcade-py/commit/f6d1892c845ee924614c174ef5bd24241b111c8c))
+* **internal:** refactor retries to not use recursion ([709debf](https://github.com/ArcadeAI/arcade-py/commit/709debf17814c59c6048b996195090ee677119ed))
+* **internal:** remove extra empty newlines ([#131](https://github.com/ArcadeAI/arcade-py/issues/131)) ([4a0f409](https://github.com/ArcadeAI/arcade-py/commit/4a0f4094477d25a8c213f07e33f42296864d2866))
+* **internal:** remove trailing character ([#140](https://github.com/ArcadeAI/arcade-py/issues/140)) ([850838e](https://github.com/ArcadeAI/arcade-py/commit/850838edc42110c22d2c4342e4340f8322fb0e86))
+* **internal:** slight transform perf improvement ([#142](https://github.com/ArcadeAI/arcade-py/issues/142)) ([af27cc2](https://github.com/ArcadeAI/arcade-py/commit/af27cc24050a658f27d49909b0596b41309c1326))
+* **internal:** update models test ([ced3495](https://github.com/ArcadeAI/arcade-py/commit/ced349577a3c0c5b033ac53ebd239f333bc13fdf))
+* **internal:** update pyright settings ([950f294](https://github.com/ArcadeAI/arcade-py/commit/950f294ea47c8aa6ac423b6ffd2cec37278a4b4b))
+
+
+### Documentation
+
+* revise readme docs about nested params ([#128](https://github.com/ArcadeAI/arcade-py/issues/128)) ([52f818d](https://github.com/ArcadeAI/arcade-py/commit/52f818d5bdee294add53ebc8257c6d3f48cbdd65))
+* swap examples used in readme ([#141](https://github.com/ArcadeAI/arcade-py/issues/141)) ([fe028d0](https://github.com/ArcadeAI/arcade-py/commit/fe028d08aa5db60e540bca6f0635911ffcb79486))
+
## 1.3.1 (2025-03-11)
Full Changelog: [v1.3.0...v1.3.1](https://github.com/ArcadeAI/arcade-py/compare/v1.3.0...v1.3.1)
diff --git a/pyproject.toml b/pyproject.toml
index 2e6932da..fee4c322 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "arcadepy"
-version = "1.3.1"
+version = "1.4.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 710f3552..a532eb91 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.3.1" # x-release-please-version
+__version__ = "1.4.0" # x-release-please-version
From d7b291cb799b1fb7a7ce08b17d2e8edbc3f071d0 Mon Sep 17 00:00:00 2001
From: sdreyer
Date: Thu, 1 May 2025 13:03:20 -0700
Subject: [PATCH 40/40] Cap certifi version
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index fee4c322..38b33dbd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,6 +14,7 @@ dependencies = [
"anyio>=3.5.0, <5",
"distro>=1.7.0, <2",
"sniffio",
+ "certifi<=2025.1.31",
]
requires-python = ">= 3.8"
classifiers = [