Skip to content

Commit 797daee

Browse files
release: 1.2.0 (#114)
* chore(internal): change default timeout to an int (#113) * chore(internal): bummp ruff dependency (#115) * feat(client): send `X-Stainless-Read-Timeout` header (#117) * chore(internal): fix type traversing dictionary params (#118) * chore(internal): minor type handling changes (#119) --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 0a3c371 commit 797daee

10 files changed

+61
-13
lines changed

Diff for: .release-please-manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.1.0"
2+
".": "1.2.0"
33
}

Diff for: CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 1.2.0 (2025-02-11)
4+
5+
Full Changelog: [v1.1.0...v1.2.0](https://github.com/browserbase/sdk-python/compare/v1.1.0...v1.2.0)
6+
7+
### Features
8+
9+
* **client:** send `X-Stainless-Read-Timeout` header ([#117](https://github.com/browserbase/sdk-python/issues/117)) ([e53c47a](https://github.com/browserbase/sdk-python/commit/e53c47ae14f4dca507cc146b37b81d5e59845806))
10+
11+
12+
### Chores
13+
14+
* **internal:** bummp ruff dependency ([#115](https://github.com/browserbase/sdk-python/issues/115)) ([f687590](https://github.com/browserbase/sdk-python/commit/f68759062445e8336ca0f6c9b0bde3b0d2ca1e62))
15+
* **internal:** change default timeout to an int ([#113](https://github.com/browserbase/sdk-python/issues/113)) ([081bb21](https://github.com/browserbase/sdk-python/commit/081bb216f4b9a4df0dfdd51bcbcacef0154fe636))
16+
* **internal:** fix type traversing dictionary params ([#118](https://github.com/browserbase/sdk-python/issues/118)) ([cc59fe8](https://github.com/browserbase/sdk-python/commit/cc59fe8950fa4e66ee5efd598b69da9c0c8f08a0))
17+
* **internal:** minor type handling changes ([#119](https://github.com/browserbase/sdk-python/issues/119)) ([7be3940](https://github.com/browserbase/sdk-python/commit/7be3940cfb0bb947a6774ec225b5eb450a951e88))
18+
319
## 1.1.0 (2025-01-28)
420

521
Full Changelog: [v1.0.5...v1.1.0](https://github.com/browserbase/sdk-python/compare/v1.0.5...v1.1.0)

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "browserbase"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
description = "The official Python library for the Browserbase API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -186,7 +186,7 @@ select = [
186186
"T201",
187187
"T203",
188188
# misuse of typing.TYPE_CHECKING
189-
"TCH004",
189+
"TC004",
190190
# import rules
191191
"TID251",
192192
]

Diff for: scripts/utils/ruffen-docs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def _md_match(match: Match[str]) -> str:
4747
with _collect_error(match):
4848
code = format_code_block(code)
4949
code = textwrap.indent(code, match["indent"])
50-
return f'{match["before"]}{code}{match["after"]}'
50+
return f"{match['before']}{code}{match['after']}"
5151

5252
def _pycon_match(match: Match[str]) -> str:
5353
code = ""
@@ -97,7 +97,7 @@ def finish_fragment() -> None:
9797
def _md_pycon_match(match: Match[str]) -> str:
9898
code = _pycon_match(match)
9999
code = textwrap.indent(code, match["indent"])
100-
return f'{match["before"]}{code}{match["after"]}'
100+
return f"{match['before']}{code}{match['after']}"
101101

102102
src = MD_RE.sub(_md_match, src)
103103
src = MD_PYCON_RE.sub(_md_pycon_match, src)

Diff for: src/browserbase/_base_client.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -418,10 +418,17 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0
418418
if idempotency_header and options.method.lower() != "get" and idempotency_header not in headers:
419419
headers[idempotency_header] = options.idempotency_key or self._idempotency_key()
420420

421-
# Don't set the retry count header if it was already set or removed by the caller. We check
421+
# Don't set these headers if they were already set or removed by the caller. We check
422422
# `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
423-
if "x-stainless-retry-count" not in (header.lower() for header in custom_headers):
423+
lower_custom_headers = [header.lower() for header in custom_headers]
424+
if "x-stainless-retry-count" not in lower_custom_headers:
424425
headers["x-stainless-retry-count"] = str(retries_taken)
426+
if "x-stainless-read-timeout" not in lower_custom_headers:
427+
timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
428+
if isinstance(timeout, Timeout):
429+
timeout = timeout.read
430+
if timeout is not None:
431+
headers["x-stainless-read-timeout"] = str(timeout)
425432

426433
return headers
427434

Diff for: src/browserbase/_constants.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
77

88
# default timeout is 1 minute
9-
DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0)
9+
DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
1010
DEFAULT_MAX_RETRIES = 2
1111
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
1212

Diff for: src/browserbase/_models.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def to_json(
172172
@override
173173
def __str__(self) -> str:
174174
# mypy complains about an invalid self arg
175-
return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc]
175+
return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc]
176176

177177
# Override the 'construct' method in a way that supports recursive parsing without validation.
178178
# Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
@@ -426,10 +426,16 @@ def construct_type(*, value: object, type_: object) -> object:
426426
427427
If the given value does not match the expected type then it is returned as-is.
428428
"""
429+
430+
# store a reference to the original type we were given before we extract any inner
431+
# types so that we can properly resolve forward references in `TypeAliasType` annotations
432+
original_type = None
433+
429434
# we allow `object` as the input type because otherwise, passing things like
430435
# `Literal['value']` will be reported as a type error by type checkers
431436
type_ = cast("type[object]", type_)
432437
if is_type_alias_type(type_):
438+
original_type = type_ # type: ignore[unreachable]
433439
type_ = type_.__value__ # type: ignore[unreachable]
434440

435441
# unwrap `Annotated[T, ...]` -> `T`
@@ -446,7 +452,7 @@ def construct_type(*, value: object, type_: object) -> object:
446452

447453
if is_union(origin):
448454
try:
449-
return validate_type(type_=cast("type[object]", type_), value=value)
455+
return validate_type(type_=cast("type[object]", original_type or type_), value=value)
450456
except Exception:
451457
pass
452458

Diff for: src/browserbase/_utils/_transform.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
is_annotated_type,
2626
strip_annotated_type,
2727
)
28-
from .._compat import model_dump, is_typeddict
28+
from .._compat import get_origin, model_dump, is_typeddict
2929

3030
_T = TypeVar("_T")
3131

@@ -164,9 +164,14 @@ def _transform_recursive(
164164
inner_type = annotation
165165

166166
stripped_type = strip_annotated_type(inner_type)
167+
origin = get_origin(stripped_type) or stripped_type
167168
if is_typeddict(stripped_type) and is_mapping(data):
168169
return _transform_typeddict(data, stripped_type)
169170

171+
if origin == dict and is_mapping(data):
172+
items_type = get_args(stripped_type)[1]
173+
return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
174+
170175
if (
171176
# List[T]
172177
(is_list_type(stripped_type) and is_list(data))
@@ -307,9 +312,14 @@ async def _async_transform_recursive(
307312
inner_type = annotation
308313

309314
stripped_type = strip_annotated_type(inner_type)
315+
origin = get_origin(stripped_type) or stripped_type
310316
if is_typeddict(stripped_type) and is_mapping(data):
311317
return await _async_transform_typeddict(data, stripped_type)
312318

319+
if origin == dict and is_mapping(data):
320+
items_type = get_args(stripped_type)[1]
321+
return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
322+
313323
if (
314324
# List[T]
315325
(is_list_type(stripped_type) and is_list(data))

Diff for: src/browserbase/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "browserbase"
4-
__version__ = "1.1.0" # x-release-please-version
4+
__version__ = "1.2.0" # x-release-please-version

Diff for: tests/test_transform.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import io
44
import pathlib
5-
from typing import Any, List, Union, TypeVar, Iterable, Optional, cast
5+
from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast
66
from datetime import date, datetime
77
from typing_extensions import Required, Annotated, TypedDict
88

@@ -388,6 +388,15 @@ def my_iter() -> Iterable[Baz8]:
388388
}
389389

390390

391+
@parametrize
392+
@pytest.mark.asyncio
393+
async def test_dictionary_items(use_async: bool) -> None:
394+
class DictItems(TypedDict):
395+
foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
396+
397+
assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}}
398+
399+
391400
class TypedDictIterableUnionStr(TypedDict):
392401
foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]
393402

0 commit comments

Comments
 (0)