From 6bbf5f154ffc5f22e5d6c3217f52937152496fd2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 03:14:35 +0000 Subject: [PATCH 01/20] 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 1f82860b86f11d32190c56888a72f236fde32409 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Apr 2025 03:18:05 +0000 Subject: [PATCH 02/20] 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 f8f5c8029f1db992a52d62c14e51478467457796 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 03:01:46 +0000 Subject: [PATCH 03/20] fix(perf): skip traversing types for NotGiven values --- src/steel/_utils/_transform.py | 11 +++++++++++ tests/test_transform.py | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/steel/_utils/_transform.py b/src/steel/_utils/_transform.py index 3ec62081..3b2b8e00 100644 --- a/src/steel/_utils/_transform.py +++ b/src/steel/_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 182a1525..95609183 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from steel._types import Base64FileInput +from steel._types import NOT_GIVEN, Base64FileInput from steel._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 ba59e3019648f08568a9bb12053b4a5eb9ded30c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 03:02:57 +0000 Subject: [PATCH 04/20] fix(perf): optimize some hot paths --- src/steel/_utils/_transform.py | 14 +++++++++++++- src/steel/_utils/_typing.py | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/steel/_utils/_transform.py b/src/steel/_utils/_transform.py index 3b2b8e00..b0cc20a7 100644 --- a/src/steel/_utils/_transform.py +++ b/src/steel/_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/steel/_utils/_typing.py b/src/steel/_utils/_typing.py index 278749b1..1958820f 100644 --- a/src/steel/_utils/_typing.py +++ b/src/steel/_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 8805d955fd7c4e20c3dd28df8e21d9077144b963 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 03:20:28 +0000 Subject: [PATCH 05/20] chore(internal): update pyright settings --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 78858e11..2bc7acd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,6 +147,7 @@ exclude = [ ] reportImplicitOverride = true +reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false From e00651abf449f04eae08a670c4df90afefc6047a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 03:21:55 +0000 Subject: [PATCH 06/20] chore(client): minor internal fixes --- src/steel/_base_client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/steel/_base_client.py b/src/steel/_base_client.py index 3142616c..ac8bacda 100644 --- a/src/steel/_base_client.py +++ b/src/steel/_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 e851175e65f28d0213b220fed1dee1223e7b98aa Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 03:07:15 +0000 Subject: [PATCH 07/20] chore(internal): bump pyright version --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- src/steel/_base_client.py | 6 +++++- src/steel/_models.py | 1 - src/steel/_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 2bc7acd7..75f09bbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ Repository = "https://github.com/steel-dev/steel-python" 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 4a36903a..10d7fdb0 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/steel/_base_client.py b/src/steel/_base_client.py index ac8bacda..5b8e5979 100644 --- a/src/steel/_base_client.py +++ b/src/steel/_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/steel/_models.py b/src/steel/_models.py index 34935716..58b9263e 100644 --- a/src/steel/_models.py +++ b/src/steel/_models.py @@ -19,7 +19,6 @@ ) import pydantic -import pydantic.generics from pydantic.fields import FieldInfo from ._types import ( diff --git a/src/steel/_utils/_typing.py b/src/steel/_utils/_typing.py index 1958820f..1bac9542 100644 --- a/src/steel/_utils/_typing.py +++ b/src/steel/_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 68b4d6fa..7434ff13 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from steel import Steel, AsyncSteel 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 afa88778..6ef9afde 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 6a1038db1f49a8b232614452dbdeda94ee920c28 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 03:08:09 +0000 Subject: [PATCH 08/20] chore(internal): base client updates --- src/steel/_base_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/steel/_base_client.py b/src/steel/_base_client.py index 5b8e5979..a3efc19d 100644 --- a/src/steel/_base_client.py +++ b/src/steel/_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 2ea55a4676fbac3339928f098080794648f15240 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 03:12:58 +0000 Subject: [PATCH 09/20] 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 6ef9afde..27133822 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 48e74c72ffbc4b3fa291f817ebd6a846a7c85624 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 00:01:16 +0000 Subject: [PATCH 10/20] feat(api): api update --- .stats.yml | 4 +- src/steel/types/session_context.py | 52 +++++++++++++++++----- src/steel/types/session_create_params.py | 48 ++++++++++++++++---- src/steel/types/session_events_response.py | 4 +- tests/api_resources/test_sessions.py | 24 +++++++++- 5 files changed, 106 insertions(+), 26 deletions(-) diff --git a/.stats.yml b/.stats.yml index 772c3914..4b8c6758 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/nen-labs%2Fsteel-6f475030f25058d2a692446a9ce20a095e0a8b4c3f73627841d274d4141fb48f.yml -openapi_spec_hash: ebd8e01a380d203620785ca2cbd9e1b9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/nen-labs%2Fsteel-c8306e25d5c8e0d16318b9d44a683dd3d10f8d8b3d56ecbd1952d7f9e95d7f08.yml +openapi_spec_hash: 9877212f13f31009e05d8a1f8b2dd750 config_hash: f23d5011c9a89d67725b48e96ffb7c99 diff --git a/src/steel/types/session_context.py b/src/steel/types/session_context.py index 65dc674e..d50b1ed2 100644 --- a/src/steel/types/session_context.py +++ b/src/steel/types/session_context.py @@ -11,34 +11,64 @@ class Cookie(BaseModel): - domain: str - """Domain the cookie belongs to""" - name: str - """Name of the cookie""" + """The name of the cookie""" value: str - """Value of the cookie""" + """The value of the cookie""" + + domain: Optional[str] = None + """The domain of the cookie""" expires: Optional[float] = None - """Unix timestamp when the cookie expires""" + """The expiration date of the cookie""" http_only: Optional[bool] = FieldInfo(alias="httpOnly", default=None) """Whether the cookie is HTTP only""" + partition_key: Optional[str] = FieldInfo(alias="partitionKey", default=None) + """The partition key of the cookie""" + path: Optional[str] = None - """Path the cookie is valid for""" + """The path of the cookie""" + + priority: Optional[Literal["Low", "Medium", "High"]] = None + """The priority of the cookie""" + + same_party: Optional[bool] = FieldInfo(alias="sameParty", default=None) + """Whether the cookie is a same party cookie""" same_site: Optional[Literal["Strict", "Lax", "None"]] = FieldInfo(alias="sameSite", default=None) - """SameSite attribute of the cookie""" + """The same site attribute of the cookie""" secure: Optional[bool] = None - """Whether the cookie requires HTTPS""" + """Whether the cookie is secure""" + + session: Optional[bool] = None + """Whether the cookie is a session cookie""" + + size: Optional[float] = None + """The size of the cookie""" + + source_port: Optional[float] = FieldInfo(alias="sourcePort", default=None) + """The source port of the cookie""" + + source_scheme: Optional[Literal["Unset", "NonSecure", "Secure"]] = FieldInfo(alias="sourceScheme", default=None) + """The source scheme of the cookie""" + + url: Optional[str] = None + """The URL of the cookie""" class SessionContext(BaseModel): cookies: Optional[List[Cookie]] = None - """Cookies from the session""" + """Cookies to initialize in the session""" + + indexed_db: Optional[Dict[str, List[Dict[str, object]]]] = FieldInfo(alias="indexedDB", default=None) + """Domain-specific indexedDB items to initialize in the session""" local_storage: Optional[Dict[str, Dict[str, object]]] = FieldInfo(alias="localStorage", default=None) - """Local storage items from the session""" + """Domain-specific localStorage items to initialize in the session""" + + session_storage: Optional[Dict[str, Dict[str, object]]] = FieldInfo(alias="sessionStorage", default=None) + """Domain-specific sessionStorage items to initialize in the session""" diff --git a/src/steel/types/session_create_params.py b/src/steel/types/session_create_params.py index 6a39fae7..2b1643f2 100644 --- a/src/steel/types/session_create_params.py +++ b/src/steel/types/session_create_params.py @@ -67,34 +67,64 @@ class Dimensions(TypedDict, total=False): class SessionContextCookie(TypedDict, total=False): - domain: Required[str] - """Domain the cookie belongs to""" - name: Required[str] - """Name of the cookie""" + """The name of the cookie""" value: Required[str] - """Value of the cookie""" + """The value of the cookie""" + + domain: str + """The domain of the cookie""" expires: float - """Unix timestamp when the cookie expires""" + """The expiration date of the cookie""" http_only: Annotated[bool, PropertyInfo(alias="httpOnly")] """Whether the cookie is HTTP only""" + partition_key: Annotated[str, PropertyInfo(alias="partitionKey")] + """The partition key of the cookie""" + path: str - """Path the cookie is valid for""" + """The path of the cookie""" + + priority: Literal["Low", "Medium", "High"] + """The priority of the cookie""" + + same_party: Annotated[bool, PropertyInfo(alias="sameParty")] + """Whether the cookie is a same party cookie""" same_site: Annotated[Literal["Strict", "Lax", "None"], PropertyInfo(alias="sameSite")] - """SameSite attribute of the cookie""" + """The same site attribute of the cookie""" secure: bool - """Whether the cookie requires HTTPS""" + """Whether the cookie is secure""" + + session: bool + """Whether the cookie is a session cookie""" + + size: float + """The size of the cookie""" + + source_port: Annotated[float, PropertyInfo(alias="sourcePort")] + """The source port of the cookie""" + + source_scheme: Annotated[Literal["Unset", "NonSecure", "Secure"], PropertyInfo(alias="sourceScheme")] + """The source scheme of the cookie""" + + url: str + """The URL of the cookie""" class SessionContext(TypedDict, total=False): cookies: Iterable[SessionContextCookie] """Cookies to initialize in the session""" + indexed_db: Annotated[Dict[str, Iterable[Dict[str, object]]], PropertyInfo(alias="indexedDB")] + """Domain-specific indexedDB items to initialize in the session""" + local_storage: Annotated[Dict[str, Dict[str, object]], PropertyInfo(alias="localStorage")] """Domain-specific localStorage items to initialize in the session""" + + session_storage: Annotated[Dict[str, Dict[str, object]], PropertyInfo(alias="sessionStorage")] + """Domain-specific sessionStorage items to initialize in the session""" diff --git a/src/steel/types/session_events_response.py b/src/steel/types/session_events_response.py index 3d39f22d..b6e7a583 100644 --- a/src/steel/types/session_events_response.py +++ b/src/steel/types/session_events_response.py @@ -1,8 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List +from typing import List from typing_extensions import TypeAlias __all__ = ["SessionEventsResponse"] -SessionEventsResponse: TypeAlias = List[Dict[str, object]] +SessionEventsResponse: TypeAlias = List[object] diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 75a45cf9..55c9fed8 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -45,17 +45,27 @@ def test_method_create_with_all_params(self, client: Steel) -> None: session_context={ "cookies": [ { - "domain": "domain", "name": "name", "value": "value", + "domain": "domain", "expires": 0, "http_only": True, + "partition_key": "partitionKey", "path": "path", + "priority": "Low", + "same_party": True, "same_site": "Strict", "secure": True, + "session": True, + "size": 0, + "source_port": 0, + "source_scheme": "Unset", + "url": "url", } ], + "indexed_db": {"foo": [{"foo": "bar"}]}, "local_storage": {"foo": {"foo": "bar"}}, + "session_storage": {"foo": {"foo": "bar"}}, }, session_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", solve_captcha=True, @@ -357,17 +367,27 @@ async def test_method_create_with_all_params(self, async_client: AsyncSteel) -> session_context={ "cookies": [ { - "domain": "domain", "name": "name", "value": "value", + "domain": "domain", "expires": 0, "http_only": True, + "partition_key": "partitionKey", "path": "path", + "priority": "Low", + "same_party": True, "same_site": "Strict", "secure": True, + "session": True, + "size": 0, + "source_port": 0, + "source_scheme": "Unset", + "url": "url", } ], + "indexed_db": {"foo": [{"foo": "bar"}]}, "local_storage": {"foo": {"foo": "bar"}}, + "session_storage": {"foo": {"foo": "bar"}}, }, session_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", solve_captcha=True, From 253270eebeaf387959a590a53c8de53a8f2efc5d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 03:56:17 +0000 Subject: [PATCH 11/20] 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 176a86e0ec58118429fcad1814aaf4e7413e7b18 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 03:56:49 +0000 Subject: [PATCH 12/20] chore(internal): import reformatting --- src/steel/resources/sessions/files.py | 5 +---- src/steel/resources/sessions/sessions.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/steel/resources/sessions/files.py b/src/steel/resources/sessions/files.py index 8adc7d22..c2c005cc 100644 --- a/src/steel/resources/sessions/files.py +++ b/src/steel/resources/sessions/files.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/steel/resources/sessions/sessions.py b/src/steel/resources/sessions/sessions.py index 1cc41f6f..9e67e7f0 100644 --- a/src/steel/resources/sessions/sessions.py +++ b/src/steel/resources/sessions/sessions.py @@ -16,10 +16,7 @@ ) from ...types import session_list_params, session_create_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 ( From f3e1d8c0cc3f8954444569bf15230860db265ccf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 03:58:22 +0000 Subject: [PATCH 13/20] chore(internal): fix list file params --- src/steel/_utils/_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/steel/_utils/_utils.py b/src/steel/_utils/_utils.py index e5811bba..ea3cf3f2 100644 --- a/src/steel/_utils/_utils.py +++ b/src/steel/_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 df55de6b59ca3c94a598327b6e787a3e47011d7c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 03:59:01 +0000 Subject: [PATCH 14/20] chore(internal): refactor retries to not use recursion --- src/steel/_base_client.py | 414 ++++++++++++++++---------------------- 1 file changed, 175 insertions(+), 239 deletions(-) diff --git a/src/steel/_base_client.py b/src/steel/_base_client.py index a3efc19d..4cc482f3 100644 --- a/src/steel/_base_client.py +++ b/src/steel/_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 c96c43bb09412aaf8033ae9697c65c0142130d16 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 03:59:35 +0000 Subject: [PATCH 15/20] fix(pydantic v1): more robust ModelField.annotation check --- src/steel/_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/steel/_models.py b/src/steel/_models.py index 58b9263e..798956f1 100644 --- a/src/steel/_models.py +++ b/src/steel/_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 ca3dd6a6647618e16525f759d9757e9823fa924b 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:45:28 +0000 Subject: [PATCH 16/20] chore(internal): minor formatting changes --- src/steel/types/pdf_response.py | 1 - src/steel/types/screenshot_response.py | 1 - src/steel/types/session_release_all_response.py | 1 - src/steel/types/session_release_response.py | 1 - 4 files changed, 4 deletions(-) diff --git a/src/steel/types/pdf_response.py b/src/steel/types/pdf_response.py index 0a05529b..ce50c1c5 100644 --- a/src/steel/types/pdf_response.py +++ b/src/steel/types/pdf_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["PdfResponse"] diff --git a/src/steel/types/screenshot_response.py b/src/steel/types/screenshot_response.py index 1bf7387c..5a352981 100644 --- a/src/steel/types/screenshot_response.py +++ b/src/steel/types/screenshot_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["ScreenshotResponse"] diff --git a/src/steel/types/session_release_all_response.py b/src/steel/types/session_release_all_response.py index d0dfc25a..22084df3 100644 --- a/src/steel/types/session_release_all_response.py +++ b/src/steel/types/session_release_all_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["SessionReleaseAllResponse"] diff --git a/src/steel/types/session_release_response.py b/src/steel/types/session_release_response.py index 5f9ed8cf..ce31b8eb 100644 --- a/src/steel/types/session_release_response.py +++ b/src/steel/types/session_release_response.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - from .._models import BaseModel __all__ = ["SessionReleaseResponse"] From f9a063f90bdfe70af335ea73e20b86201fd7020d 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:46:00 +0000 Subject: [PATCH 17/20] 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 4e018871..d40b5fc8 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 c52ccfa2..3174a0f3 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 == 'steel-dev/steel-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: From 01c88a56c6c1690bab8805a19aae7cd70c5c5a4a 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:46:35 +0000 Subject: [PATCH 18/20] 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..e0ba203e 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/steel-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/steel-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 d40b5fc8..4e018871 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 3174a0f3..c52ccfa2 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 == 'steel-dev/steel-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: From 6b4eda4ff26b75e94e067bd278fa47551ddee962 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:48:02 +0000 Subject: [PATCH 19/20] chore: broadly detect json family of content-type headers --- src/steel/_response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/steel/_response.py b/src/steel/_response.py index d4be760b..4a4b497b 100644 --- a/src/steel/_response.py +++ b/src/steel/_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 672bd82557ef1a11a97375ac16fade7208b74462 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:55:38 +0000 Subject: [PATCH 20/20] release: 0.2.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/steel/_version.py | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3d2ac0bd..10f30916 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0" + ".": "0.2.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1d2a13..aad2b2f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## 0.2.0 (2025-04-24) + +Full Changelog: [v0.1.0...v0.2.0](https://github.com/steel-dev/steel-python/compare/v0.1.0...v0.2.0) + +### Features + +* **api:** api update ([48e74c7](https://github.com/steel-dev/steel-python/commit/48e74c72ffbc4b3fa291f817ebd6a846a7c85624)) + + +### Bug Fixes + +* **perf:** optimize some hot paths ([ba59e30](https://github.com/steel-dev/steel-python/commit/ba59e3019648f08568a9bb12053b4a5eb9ded30c)) +* **perf:** skip traversing types for NotGiven values ([f8f5c80](https://github.com/steel-dev/steel-python/commit/f8f5c8029f1db992a52d62c14e51478467457796)) +* **pydantic v1:** more robust ModelField.annotation check ([c96c43b](https://github.com/steel-dev/steel-python/commit/c96c43bb09412aaf8033ae9697c65c0142130d16)) + + +### Chores + +* broadly detect json family of content-type headers ([6b4eda4](https://github.com/steel-dev/steel-python/commit/6b4eda4ff26b75e94e067bd278fa47551ddee962)) +* **ci:** add timeout thresholds for CI jobs ([253270e](https://github.com/steel-dev/steel-python/commit/253270eebeaf387959a590a53c8de53a8f2efc5d)) +* **ci:** only use depot for staging repos ([01c88a5](https://github.com/steel-dev/steel-python/commit/01c88a56c6c1690bab8805a19aae7cd70c5c5a4a)) +* **client:** minor internal fixes ([e00651a](https://github.com/steel-dev/steel-python/commit/e00651abf449f04eae08a670c4df90afefc6047a)) +* **internal:** base client updates ([6a1038d](https://github.com/steel-dev/steel-python/commit/6a1038db1f49a8b232614452dbdeda94ee920c28)) +* **internal:** bump pyright version ([e851175](https://github.com/steel-dev/steel-python/commit/e851175e65f28d0213b220fed1dee1223e7b98aa)) +* **internal:** codegen related update ([f9a063f](https://github.com/steel-dev/steel-python/commit/f9a063f90bdfe70af335ea73e20b86201fd7020d)) +* **internal:** expand CI branch coverage ([6bbf5f1](https://github.com/steel-dev/steel-python/commit/6bbf5f154ffc5f22e5d6c3217f52937152496fd2)) +* **internal:** fix list file params ([f3e1d8c](https://github.com/steel-dev/steel-python/commit/f3e1d8c0cc3f8954444569bf15230860db265ccf)) +* **internal:** import reformatting ([176a86e](https://github.com/steel-dev/steel-python/commit/176a86e0ec58118429fcad1814aaf4e7413e7b18)) +* **internal:** minor formatting changes ([ca3dd6a](https://github.com/steel-dev/steel-python/commit/ca3dd6a6647618e16525f759d9757e9823fa924b)) +* **internal:** reduce CI branch coverage ([1f82860](https://github.com/steel-dev/steel-python/commit/1f82860b86f11d32190c56888a72f236fde32409)) +* **internal:** refactor retries to not use recursion ([df55de6](https://github.com/steel-dev/steel-python/commit/df55de6b59ca3c94a598327b6e787a3e47011d7c)) +* **internal:** update models test ([2ea55a4](https://github.com/steel-dev/steel-python/commit/2ea55a4676fbac3339928f098080794648f15240)) +* **internal:** update pyright settings ([8805d95](https://github.com/steel-dev/steel-python/commit/8805d955fd7c4e20c3dd28df8e21d9077144b963)) + ## 0.1.0 (2025-04-09) Full Changelog: [v0.0.1...v0.1.0](https://github.com/steel-dev/steel-python/compare/v0.0.1...v0.1.0) diff --git a/pyproject.toml b/pyproject.toml index 75f09bbe..85fd4c9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "steel-sdk" -version = "0.1.0" +version = "0.2.0" description = "The official Python library for the steel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/steel/_version.py b/src/steel/_version.py index 761fb526..824fbb56 100644 --- a/src/steel/_version.py +++ b/src/steel/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "steel" -__version__ = "0.1.0" # x-release-please-version +__version__ = "0.2.0" # x-release-please-version