From 97edfd04ce506478373c878b68cab3a8d6adc80d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:49:00 +0000 Subject: [PATCH 01/45] feat(api): api update --- .stats.yml | 4 ++-- src/browserbase/resources/sessions/sessions.py | 8 -------- src/browserbase/types/session_create_params.py | 9 --------- tests/api_resources/test_sessions.py | 2 -- 4 files changed, 2 insertions(+), 21 deletions(-) diff --git a/.stats.yml b/.stats.yml index e1cd805..b000c8c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 18 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-0b96e0120f7cf3fba797371433e15a08d14727c0526d718b728faee615624297.yml -openapi_spec_hash: 8d007eed388933bf9d74c5488a56be41 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-be7a4aeebb1605262935b4b3ab446a95b1fad8a7d18098943dd548c8a486ef13.yml +openapi_spec_hash: 1c950a109f80140711e7ae2cf87fddad config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 5e58bbb..ceaaeb8 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -104,7 +104,6 @@ def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, - proxy_settings: session_create_params.ProxySettings | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -132,8 +131,6 @@ def create( proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. - proxy_settings: [NOT IN DOCS] Supplementary proxy settings. Optional. - region: The region where the Session should run. api_timeout: Duration in seconds after which the session will automatically end. Defaults to @@ -159,7 +156,6 @@ def create( "extension_id": extension_id, "keep_alive": keep_alive, "proxies": proxies, - "proxy_settings": proxy_settings, "region": region, "api_timeout": api_timeout, "user_metadata": user_metadata, @@ -379,7 +375,6 @@ async def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, - proxy_settings: session_create_params.ProxySettings | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -407,8 +402,6 @@ async def create( proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. - proxy_settings: [NOT IN DOCS] Supplementary proxy settings. Optional. - region: The region where the Session should run. api_timeout: Duration in seconds after which the session will automatically end. Defaults to @@ -434,7 +427,6 @@ async def create( "extension_id": extension_id, "keep_alive": keep_alive, "proxies": proxies, - "proxy_settings": proxy_settings, "region": region, "api_timeout": api_timeout, "user_metadata": user_metadata, diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 7fafe44..3a517c0 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -19,7 +19,6 @@ "ProxiesUnionMember0UnionMember0", "ProxiesUnionMember0UnionMember0Geolocation", "ProxiesUnionMember0UnionMember1", - "ProxySettings", ] @@ -50,9 +49,6 @@ class SessionCreateParams(TypedDict, total=False): Can be true for default proxy, or an array of proxy configurations. """ - proxy_settings: Annotated[ProxySettings, PropertyInfo(alias="proxySettings")] - """[NOT IN DOCS] Supplementary proxy settings. Optional.""" - region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] """The region where the Session should run.""" @@ -212,8 +208,3 @@ class ProxiesUnionMember0UnionMember1(TypedDict, total=False): ProxiesUnionMember0: TypeAlias = Union[ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1] - - -class ProxySettings(TypedDict, total=False): - ca_certificates: Required[Annotated[SequenceNotStr[str], PropertyInfo(alias="caCertificates")]] - """[NOT IN DOCS] The TLS certificate IDs to trust. Optional.""" diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 24da8f0..7a16f64 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -79,7 +79,6 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: }, } ], - proxy_settings={"ca_certificates": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"]}, region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -327,7 +326,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas }, } ], - proxy_settings={"ca_certificates": ["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"]}, region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, From ea60157e52c3f8477ecf20f2a39ec2a722c83fed Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 02:36:32 +0000 Subject: [PATCH 02/45] chore(internal): detect missing future annotations with ruff --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 217c1da..26001f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -233,6 +233,8 @@ select = [ "B", # remove unused imports "F401", + # check for missing future annotations + "FA102", # bare except statements "E722", # unused arguments @@ -255,6 +257,8 @@ unfixable = [ "T203", ] +extend-safe-fixes = ["FA102"] + [tool.ruff.lint.flake8-tidy-imports.banned-api] "functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead" From 42685a189cf7a465d8696fbc8902123567f1e9e0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:22:48 +0000 Subject: [PATCH 03/45] chore: bump `httpx-aiohttp` version to 0.1.9 --- pyproject.toml | 2 +- requirements-dev.lock | 2 +- requirements.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 26001f9..4a91785 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ Homepage = "https://github.com/browserbase/sdk-python" Repository = "https://github.com/browserbase/sdk-python" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index e600824..115b339 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -67,7 +67,7 @@ httpx==0.28.1 # via browserbase # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.8 +httpx-aiohttp==0.1.9 # via browserbase idna==3.10 # via anyio diff --git a/requirements.lock b/requirements.lock index 2495a26..55ea883 100644 --- a/requirements.lock +++ b/requirements.lock @@ -43,7 +43,7 @@ httpcore==1.0.9 httpx==0.28.1 # via browserbase # via httpx-aiohttp -httpx-aiohttp==0.1.8 +httpx-aiohttp==0.1.9 # via browserbase idna==3.4 # via anyio From bb617bee85cc709ea14c4b53eac06058f28318e9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 02:46:27 +0000 Subject: [PATCH 04/45] fix(client): close streams without requiring full consumption --- src/browserbase/_streaming.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/browserbase/_streaming.py b/src/browserbase/_streaming.py index c04b233..129714a 100644 --- a/src/browserbase/_streaming.py +++ b/src/browserbase/_streaming.py @@ -57,9 +57,8 @@ def __stream__(self) -> Iterator[_T]: for sse in iterator: yield process_data(data=sse.json(), cast_to=cast_to, response=response) - # Ensure the entire stream is consumed - for _sse in iterator: - ... + # As we might not fully consume the response stream, we need to close it explicitly + response.close() def __enter__(self) -> Self: return self @@ -121,9 +120,8 @@ async def __stream__(self) -> AsyncIterator[_T]: async for sse in iterator: yield process_data(data=sse.json(), cast_to=cast_to, response=response) - # Ensure the entire stream is consumed - async for _sse in iterator: - ... + # As we might not fully consume the response stream, we need to close it explicitly + await response.aclose() async def __aenter__(self) -> Self: return self From 6ca21dcf117d076b9baaa43e0b1efd676c518845 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 03:57:17 +0000 Subject: [PATCH 05/45] chore(internal/tests): avoid race condition with implicit client cleanup --- tests/test_client.py | 364 ++++++++++++++++++++++++------------------- 1 file changed, 200 insertions(+), 164 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index aed68ba..db556fc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -59,51 +59,49 @@ def _get_open_connections(client: Browserbase | AsyncBrowserbase) -> int: class TestBrowserbase: - client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter) -> None: + def test_raw_response(self, respx_mock: MockRouter, client: Browserbase) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + def test_raw_response_for_binary(self, respx_mock: MockRouter, client: Browserbase) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = self.client.post("/foo", cast_to=httpx.Response) + response = client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, client: Browserbase) -> None: + copied = client.copy() + assert id(copied) != id(client) - copied = self.client.copy(api_key="another My API Key") + copied = client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, client: Browserbase) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(client.timeout, httpx.Timeout) + copied = client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: client = Browserbase( @@ -138,6 +136,7 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + client.close() def test_copy_default_query(self) -> None: client = Browserbase( @@ -175,13 +174,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + client.close() + + def test_copy_signature(self, client: Browserbase) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -192,12 +193,12 @@ def test_copy_signature(self) -> None: assert copy_param is not None, f"copy() signature is missing the {name} param" @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") - def test_copy_build_request(self) -> None: + def test_copy_build_request(self, client: Browserbase) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -254,14 +255,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + def test_request_timeout(self, client: Browserbase) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( - FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) - ) + request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(100.0) @@ -274,6 +273,8 @@ def test_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + client.close() + def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: @@ -285,6 +286,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + client.close() + # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = Browserbase( @@ -295,6 +298,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + client.close() + # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = Browserbase( @@ -305,6 +310,8 @@ def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + client.close() + async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: @@ -316,14 +323,14 @@ async def test_invalid_http_client(self) -> None: ) def test_default_headers_option(self) -> None: - client = Browserbase( + test_client = Browserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = Browserbase( + test_client2 = Browserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -332,10 +339,13 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + test_client.close() + test_client2.close() + def test_validate_headers(self) -> None: client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -364,8 +374,10 @@ def test_default_query_option(self) -> None: url = httpx.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + client.close() + + def test_request_extra_json(self, client: Browserbase) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -376,7 +388,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -387,7 +399,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -398,8 +410,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: Browserbase) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -409,7 +421,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -420,8 +432,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: Browserbase) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -434,7 +446,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -448,7 +460,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -491,7 +503,7 @@ def test_multipart_repeating_array(self, client: Browserbase) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter) -> None: + def test_basic_union_response(self, respx_mock: MockRouter, client: Browserbase) -> None: class Model1(BaseModel): name: str @@ -500,12 +512,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + def test_union_response_different_types(self, respx_mock: MockRouter, client: Browserbase) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -516,18 +528,18 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: Browserbase) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -543,7 +555,7 @@ class Model(BaseModel): ) ) - response = self.client.get("/foo", cast_to=Model) + response = client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 @@ -557,6 +569,8 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" + client.close() + def test_base_url_env(self) -> None: with update_env(BROWSERBASE_BASE_URL="http://localhost:5000/from/env"): client = Browserbase(api_key=api_key, _strict_response_validation=True) @@ -586,6 +600,7 @@ def test_base_url_trailing_slash(self, client: Browserbase) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -611,6 +626,7 @@ def test_base_url_no_trailing_slash(self, client: Browserbase) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + client.close() @pytest.mark.parametrize( "client", @@ -636,35 +652,36 @@ def test_absolute_request_url(self, client: Browserbase) -> None: ), ) assert request.url == "https://myapi.com/foo" + client.close() def test_copied_client_does_not_close_http(self) -> None: - client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied - assert not client.is_closed() + assert not test_client.is_closed() def test_client_context_manager(self) -> None: - client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - with client as c2: - assert c2 is client + test_client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) + with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: Browserbase) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - self.client.get("/foo", cast_to=Model) + client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) @@ -686,11 +703,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=False) - response = client.get("/foo", cast_to=Model) + response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + strict_client.close() + non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -713,9 +733,9 @@ class Model(BaseModel): ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Browserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, client: Browserbase + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) @@ -729,7 +749,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien with pytest.raises(APITimeoutError): client.sessions.with_streaming_response.create(project_id="projectId").__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) @@ -738,7 +758,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client with pytest.raises(APIStatusError): client.sessions.with_streaming_response.create(project_id="projectId").__enter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -844,83 +864,77 @@ def test_default_client_creation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - def test_follow_redirects(self, respx_mock: MockRouter) -> None: + def test_follow_redirects(self, respx_mock: MockRouter, client: Browserbase) -> None: # Test that the default follow_redirects=True allows following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) ) respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) - response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) assert response.status_code == 200 assert response.json() == {"status": "ok"} @pytest.mark.respx(base_url=base_url) - def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Browserbase) -> None: # Test that follow_redirects=False prevents following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) ) with pytest.raises(APIStatusError) as exc_info: - self.client.post( - "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response - ) + client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response) assert exc_info.value.response.status_code == 302 assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" class TestAsyncBrowserbase: - client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response(self, respx_mock: MockRouter) -> None: + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None: + async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) - response = await self.client.post("/foo", cast_to=httpx.Response) + response = await async_client.post("/foo", cast_to=httpx.Response) assert response.status_code == 200 assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self) -> None: - copied = self.client.copy() - assert id(copied) != id(self.client) + def test_copy(self, async_client: AsyncBrowserbase) -> None: + copied = async_client.copy() + assert id(copied) != id(async_client) - copied = self.client.copy(api_key="another My API Key") + copied = async_client.copy(api_key="another My API Key") assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + assert async_client.api_key == "My API Key" - def test_copy_default_options(self) -> None: + def test_copy_default_options(self, async_client: AsyncBrowserbase) -> None: # options that have a default are overridden correctly - copied = self.client.copy(max_retries=7) + copied = async_client.copy(max_retries=7) assert copied.max_retries == 7 - assert self.client.max_retries == 2 + assert async_client.max_retries == 2 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 assert copied.max_retries == 7 # timeout - assert isinstance(self.client.timeout, httpx.Timeout) - copied = self.client.copy(timeout=None) + assert isinstance(async_client.timeout, httpx.Timeout) + copied = async_client.copy(timeout=None) assert copied.timeout is None - assert isinstance(self.client.timeout, httpx.Timeout) + assert isinstance(async_client.timeout, httpx.Timeout) - def test_copy_default_headers(self) -> None: + async def test_copy_default_headers(self) -> None: client = AsyncBrowserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) @@ -953,8 +967,9 @@ def test_copy_default_headers(self) -> None: match="`default_headers` and `set_default_headers` arguments are mutually exclusive", ): client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"}) + await client.close() - def test_copy_default_query(self) -> None: + async def test_copy_default_query(self) -> None: client = AsyncBrowserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) @@ -990,13 +1005,15 @@ def test_copy_default_query(self) -> None: ): client.copy(set_default_query={}, default_query={"foo": "Bar"}) - def test_copy_signature(self) -> None: + await client.close() + + def test_copy_signature(self, async_client: AsyncBrowserbase) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. - self.client.__init__, # type: ignore[misc] + async_client.__init__, # type: ignore[misc] ) - copy_signature = inspect.signature(self.client.copy) + copy_signature = inspect.signature(async_client.copy) exclude_params = {"transport", "proxies", "_strict_response_validation"} for name in init_signature.parameters.keys(): @@ -1007,12 +1024,12 @@ def test_copy_signature(self) -> None: assert copy_param is not None, f"copy() signature is missing the {name} param" @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") - def test_copy_build_request(self) -> None: + def test_copy_build_request(self, async_client: AsyncBrowserbase) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: - client = self.client.copy() - client._build_request(options) + client_copy = async_client.copy() + client_copy._build_request(options) # ensure that the machinery is warmed up before tracing starts. build_request(options) @@ -1069,12 +1086,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - async def test_request_timeout(self) -> None: - request = self.client._build_request(FinalRequestOptions(method="get", url="/foo")) + async def test_request_timeout(self, async_client: AsyncBrowserbase) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT - request = self.client._build_request( + request = async_client._build_request( FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)) ) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -1089,6 +1106,8 @@ async def test_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(0) + await client.close() + async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: @@ -1100,6 +1119,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == httpx.Timeout(None) + await client.close() + # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncBrowserbase( @@ -1110,6 +1131,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT + await client.close() + # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncBrowserbase( @@ -1120,6 +1143,8 @@ async def test_http_client_timeout_option(self) -> None: timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT # our default + await client.close() + def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: @@ -1130,15 +1155,15 @@ def test_invalid_http_client(self) -> None: http_client=cast(Any, http_client), ) - def test_default_headers_option(self) -> None: - client = AsyncBrowserbase( + async def test_default_headers_option(self) -> None: + test_client = AsyncBrowserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - client2 = AsyncBrowserbase( + test_client2 = AsyncBrowserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1147,10 +1172,13 @@ def test_default_headers_option(self) -> None: "X-Stainless-Lang": "my-overriding-header", }, ) - request = client2._build_request(FinalRequestOptions(method="get", url="/foo")) + request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + await test_client.close() + await test_client2.close() + def test_validate_headers(self) -> None: client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1161,7 +1189,7 @@ def test_validate_headers(self) -> None: client2 = AsyncBrowserbase(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 - def test_default_query_option(self) -> None: + async def test_default_query_option(self) -> None: client = AsyncBrowserbase( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) @@ -1179,8 +1207,10 @@ def test_default_query_option(self) -> None: url = httpx.URL(request.url) assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} - def test_request_extra_json(self) -> None: - request = self.client._build_request( + await client.close() + + def test_request_extra_json(self, client: Browserbase) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1191,7 +1221,7 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": False} - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1202,7 +1232,7 @@ def test_request_extra_json(self) -> None: assert data == {"baz": False} # `extra_json` takes priority over `json_data` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1213,8 +1243,8 @@ def test_request_extra_json(self) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self) -> None: - request = self.client._build_request( + def test_request_extra_headers(self, client: Browserbase) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1224,7 +1254,7 @@ def test_request_extra_headers(self) -> None: assert request.headers.get("X-Foo") == "Foo" # `extra_headers` takes priority over `default_headers` when keys clash - request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request( + request = client.with_options(default_headers={"X-Bar": "true"})._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1235,8 +1265,8 @@ def test_request_extra_headers(self) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self) -> None: - request = self.client._build_request( + def test_request_extra_query(self, client: Browserbase) -> None: + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1249,7 +1279,7 @@ def test_request_extra_query(self) -> None: assert params == {"my_query_param": "Foo"} # if both `query` and `extra_query` are given, they are merged - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1263,7 +1293,7 @@ def test_request_extra_query(self) -> None: assert params == {"bar": "1", "foo": "2"} # `extra_query` takes priority over `query` when keys clash - request = self.client._build_request( + request = client._build_request( FinalRequestOptions( method="post", url="/foo", @@ -1306,7 +1336,7 @@ def test_multipart_repeating_array(self, async_client: AsyncBrowserbase) -> None ] @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter) -> None: + async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: class Model1(BaseModel): name: str @@ -1315,12 +1345,12 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter) -> None: + async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1331,18 +1361,20 @@ class Model2(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model2) assert response.foo == "bar" respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1})) - response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) + response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2])) assert isinstance(response, Model1) assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None: + async def test_non_application_json_content_type_for_json_data( + self, respx_mock: MockRouter, async_client: AsyncBrowserbase + ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -1358,11 +1390,11 @@ class Model(BaseModel): ) ) - response = await self.client.get("/foo", cast_to=Model) + response = await async_client.get("/foo", cast_to=Model) assert isinstance(response, Model) assert response.foo == 2 - def test_base_url_setter(self) -> None: + async def test_base_url_setter(self) -> None: client = AsyncBrowserbase( base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) @@ -1372,7 +1404,9 @@ def test_base_url_setter(self) -> None: assert client.base_url == "https://example.com/from_setter/" - def test_base_url_env(self) -> None: + await client.close() + + async def test_base_url_env(self) -> None: with update_env(BROWSERBASE_BASE_URL="http://localhost:5000/from/env"): client = AsyncBrowserbase(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @@ -1392,7 +1426,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: AsyncBrowserbase) -> None: + async def test_base_url_trailing_slash(self, client: AsyncBrowserbase) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1401,6 +1435,7 @@ def test_base_url_trailing_slash(self, client: AsyncBrowserbase) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1417,7 +1452,7 @@ def test_base_url_trailing_slash(self, client: AsyncBrowserbase) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: AsyncBrowserbase) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncBrowserbase) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1426,6 +1461,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncBrowserbase) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + await client.close() @pytest.mark.parametrize( "client", @@ -1442,7 +1478,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncBrowserbase) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: AsyncBrowserbase) -> None: + async def test_absolute_request_url(self, client: AsyncBrowserbase) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1451,37 +1487,39 @@ def test_absolute_request_url(self, client: AsyncBrowserbase) -> None: ), ) assert request.url == "https://myapi.com/foo" + await client.close() async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - assert not client.is_closed() + test_client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) + assert not test_client.is_closed() - copied = client.copy() - assert copied is not client + copied = test_client.copy() + assert copied is not test_client del copied await asyncio.sleep(0.2) - assert not client.is_closed() + assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - async with client as c2: - assert c2 is client + test_client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) + async with test_client as c2: + assert c2 is test_client assert not c2.is_closed() - assert not client.is_closed() - assert client.is_closed() + assert not test_client.is_closed() + assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio - async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None: + async def test_client_response_validation_error( + self, respx_mock: MockRouter, async_client: AsyncBrowserbase + ) -> None: class Model(BaseModel): foo: str respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}})) with pytest.raises(APIResponseValidationError) as exc: - await self.client.get("/foo", cast_to=Model) + await async_client.get("/foo", cast_to=Model) assert isinstance(exc.value.__cause__, ValidationError) @@ -1492,7 +1530,6 @@ async def test_client_max_retries_validation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: class Model(BaseModel): name: str @@ -1504,11 +1541,14 @@ class Model(BaseModel): with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=False) - response = await client.get("/foo", cast_to=Model) + response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] + await strict_client.close() + await non_strict_client.close() + @pytest.mark.parametrize( "remaining_retries,retry_after,timeout", [ @@ -1531,13 +1571,12 @@ class Model(BaseModel): ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) - @pytest.mark.asyncio - async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncBrowserbase(base_url=base_url, api_key=api_key, _strict_response_validation=True) - + async def test_parse_retry_after_header( + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncBrowserbase + ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) - calculated = client._calculate_retry_timeout(remaining_retries, options, headers) + calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @@ -1550,7 +1589,7 @@ async def test_retrying_timeout_errors_doesnt_leak( with pytest.raises(APITimeoutError): await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) @@ -1561,12 +1600,11 @@ async def test_retrying_status_errors_doesnt_leak( with pytest.raises(APIStatusError): await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() - assert _get_open_connections(self.client) == 0 + assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, @@ -1598,7 +1636,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_omit_retry_count_header( self, async_client: AsyncBrowserbase, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1624,7 +1661,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - @pytest.mark.asyncio async def test_overwrite_retry_count_header( self, async_client: AsyncBrowserbase, failures_before_success: int, respx_mock: MockRouter ) -> None: @@ -1674,26 +1710,26 @@ async def test_default_client_creation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects(self, respx_mock: MockRouter) -> None: + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: # Test that the default follow_redirects=True allows following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) ) respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) - response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) assert response.status_code == 200 assert response.json() == {"status": "ok"} @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: # Test that follow_redirects=False prevents following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) ) with pytest.raises(APIStatusError) as exc_info: - await self.client.post( + await async_client.post( "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response ) From 1d6aebda8211f3ffe1602420cfca5672d84561bd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 05:45:08 +0000 Subject: [PATCH 06/45] chore(internal): grammar fix (it's -> its) --- src/browserbase/_utils/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browserbase/_utils/_utils.py b/src/browserbase/_utils/_utils.py index 50d5926..eec7f4a 100644 --- a/src/browserbase/_utils/_utils.py +++ b/src/browserbase/_utils/_utils.py @@ -133,7 +133,7 @@ def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]: # Type safe methods for narrowing types with TypeVars. # The default narrowing for isinstance(obj, dict) is dict[unknown, unknown], # however this cause Pyright to rightfully report errors. As we know we don't -# care about the contained types we can safely use `object` in it's place. +# care about the contained types we can safely use `object` in its place. # # There are two separate functions defined, `is_*` and `is_*_t` for different use cases. # `is_*` is for when you're dealing with an unknown input From e36cf2bcad59f24078b30d5e463e0e2325f9439c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 05:30:49 +0000 Subject: [PATCH 07/45] chore(package): drop Python 3.8 support --- README.md | 4 ++-- pyproject.toml | 5 ++--- src/browserbase/_utils/_sync.py | 34 +++------------------------------ 3 files changed, 7 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 5c1155d..6edcd70 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![PyPI version](https://img.shields.io/pypi/v/browserbase.svg?label=pypi%20(stable))](https://pypi.org/project/browserbase/) -The Browserbase Python library provides convenient access to the Browserbase REST API from any Python 3.8+ +The Browserbase Python library provides convenient access to the Browserbase REST API from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). @@ -418,7 +418,7 @@ print(browserbase.__version__) ## Requirements -Python 3.8 or higher. +Python 3.9 or higher. ## Contributing diff --git a/pyproject.toml b/pyproject.toml index 4a91785..bd2dc0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,11 +15,10 @@ dependencies = [ "distro>=1.7.0, <2", "sniffio", ] -requires-python = ">= 3.8" +requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -150,7 +149,7 @@ filterwarnings = [ # there are a couple of flags that are still disabled by # default in strict mode as they are experimental and niche. typeCheckingMode = "strict" -pythonVersion = "3.8" +pythonVersion = "3.9" exclude = [ "_dev", diff --git a/src/browserbase/_utils/_sync.py b/src/browserbase/_utils/_sync.py index ad7ec71..f6027c1 100644 --- a/src/browserbase/_utils/_sync.py +++ b/src/browserbase/_utils/_sync.py @@ -1,10 +1,8 @@ from __future__ import annotations -import sys import asyncio import functools -import contextvars -from typing import Any, TypeVar, Callable, Awaitable +from typing import TypeVar, Callable, Awaitable from typing_extensions import ParamSpec import anyio @@ -15,34 +13,11 @@ T_ParamSpec = ParamSpec("T_ParamSpec") -if sys.version_info >= (3, 9): - _asyncio_to_thread = asyncio.to_thread -else: - # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread - # for Python 3.8 support - async def _asyncio_to_thread( - func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs - ) -> Any: - """Asynchronously run function *func* in a separate thread. - - Any *args and **kwargs supplied for this function are directly passed - to *func*. Also, the current :class:`contextvars.Context` is propagated, - allowing context variables from the main thread to be accessed in the - separate thread. - - Returns a coroutine that can be awaited to get the eventual result of *func*. - """ - loop = asyncio.events.get_running_loop() - ctx = contextvars.copy_context() - func_call = functools.partial(ctx.run, func, *args, **kwargs) - return await loop.run_in_executor(None, func_call) - - async def to_thread( func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs ) -> T_Retval: if sniffio.current_async_library() == "asyncio": - return await _asyncio_to_thread(func, *args, **kwargs) + return await asyncio.to_thread(func, *args, **kwargs) return await anyio.to_thread.run_sync( functools.partial(func, *args, **kwargs), @@ -53,10 +28,7 @@ async def to_thread( def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: """ Take a blocking function and create an async one that receives the same - positional and keyword arguments. For python version 3.9 and above, it uses - asyncio.to_thread to run the function in a separate thread. For python version - 3.8, it uses locally defined copy of the asyncio.to_thread function which was - introduced in python 3.9. + positional and keyword arguments. Usage: From 8f4df7c2a20dd87d54d5ca12a8060a25223d2ef5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 05:31:25 +0000 Subject: [PATCH 08/45] fix: compat with Python 3.14 --- src/browserbase/_models.py | 11 ++++++++--- tests/test_models.py | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/browserbase/_models.py b/src/browserbase/_models.py index 6a3cd1d..fcec2cf 100644 --- a/src/browserbase/_models.py +++ b/src/browserbase/_models.py @@ -2,6 +2,7 @@ import os import inspect +import weakref from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( @@ -573,6 +574,9 @@ class CachedDiscriminatorType(Protocol): __discriminator__: DiscriminatorDetails +DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary() + + class DiscriminatorDetails: field_name: str """The name of the discriminator field in the variant class, e.g. @@ -615,8 +619,9 @@ def __init__( def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None: - if isinstance(union, CachedDiscriminatorType): - return union.__discriminator__ + cached = DISCRIMINATOR_CACHE.get(union) + if cached is not None: + return cached discriminator_field_name: str | None = None @@ -669,7 +674,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, discriminator_field=discriminator_field_name, discriminator_alias=discriminator_alias, ) - cast(CachedDiscriminatorType, union).__discriminator__ = details + DISCRIMINATOR_CACHE.setdefault(union, details) return details diff --git a/tests/test_models.py b/tests/test_models.py index 34f8733..1ecdeec 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -9,7 +9,7 @@ from browserbase._utils import PropertyInfo from browserbase._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from browserbase._models import BaseModel, construct_type +from browserbase._models import DISCRIMINATOR_CACHE, BaseModel, construct_type class BasicModel(BaseModel): @@ -809,7 +809,7 @@ class B(BaseModel): UnionType = cast(Any, Union[A, B]) - assert not hasattr(UnionType, "__discriminator__") + assert not DISCRIMINATOR_CACHE.get(UnionType) m = construct_type( value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")]) @@ -818,7 +818,7 @@ class B(BaseModel): assert m.type == "b" assert m.data == "foo" # type: ignore[comparison-overlap] - discriminator = UnionType.__discriminator__ + discriminator = DISCRIMINATOR_CACHE.get(UnionType) assert discriminator is not None m = construct_type( @@ -830,7 +830,7 @@ class B(BaseModel): # if the discriminator details object stays the same between invocations then # we hit the cache - assert UnionType.__discriminator__ is discriminator + assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator @pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1") From 135104eec5555bd650d25743dd91587e9c74e549 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 05:06:22 +0000 Subject: [PATCH 09/45] fix(compat): update signatures of `model_dump` and `model_dump_json` for Pydantic v1 --- src/browserbase/_models.py | 41 +++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/browserbase/_models.py b/src/browserbase/_models.py index fcec2cf..ca9500b 100644 --- a/src/browserbase/_models.py +++ b/src/browserbase/_models.py @@ -257,15 +257,16 @@ def model_dump( mode: Literal["json", "python"] | str = "python", include: IncEx | None = None, exclude: IncEx | None = None, + context: Any | None = None, by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, - serialize_as_any: bool = False, fallback: Callable[[Any], Any] | None = None, + serialize_as_any: bool = False, ) -> dict[str, Any]: """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump @@ -273,16 +274,24 @@ def model_dump( Args: mode: The mode in which `to_python` should run. - If mode is 'json', the dictionary will only contain JSON serializable types. - If mode is 'python', the dictionary may contain any Python objects. - include: A list of fields to include in the output. - exclude: A list of fields to exclude from the output. + If mode is 'json', the output will only contain JSON serializable types. + If mode is 'python', the output may contain non-JSON-serializable Python objects. + include: A set of fields to include in the output. + exclude: A set of fields to exclude from the output. + context: Additional context to pass to the serializer. by_alias: Whether to use the field's alias in the dictionary key if defined. - exclude_unset: Whether to exclude fields that are unset or None from the output. - exclude_defaults: Whether to exclude fields that are set to their default value from the output. - exclude_none: Whether to exclude fields that have a value of `None` from the output. - round_trip: Whether to enable serialization and deserialization round-trip support. - warnings: Whether to log warnings when invalid fields are encountered. + exclude_unset: Whether to exclude fields that have not been explicitly set. + exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_none: Whether to exclude fields that have a value of `None`. + exclude_computed_fields: Whether to exclude computed fields. + While this can be useful for round-tripping, it is usually recommended to use the dedicated + `round_trip` parameter instead. + round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. + warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, + "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + fallback: A function to call when an unknown value is encountered. If not provided, + a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. + serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. Returns: A dictionary representation of the model. @@ -299,6 +308,8 @@ def model_dump( raise ValueError("serialize_as_any is only supported in Pydantic v2") if fallback is not None: raise ValueError("fallback is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") dumped = super().dict( # pyright: ignore[reportDeprecated] include=include, exclude=exclude, @@ -315,15 +326,17 @@ def model_dump_json( self, *, indent: int | None = None, + ensure_ascii: bool = False, include: IncEx | None = None, exclude: IncEx | None = None, + context: Any | None = None, by_alias: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, + exclude_computed_fields: bool = False, round_trip: bool = False, warnings: bool | Literal["none", "warn", "error"] = True, - context: dict[str, Any] | None = None, fallback: Callable[[Any], Any] | None = None, serialize_as_any: bool = False, ) -> str: @@ -355,6 +368,10 @@ def model_dump_json( raise ValueError("serialize_as_any is only supported in Pydantic v2") if fallback is not None: raise ValueError("fallback is only supported in Pydantic v2") + if ensure_ascii != False: + raise ValueError("ensure_ascii is only supported in Pydantic v2") + if exclude_computed_fields != False: + raise ValueError("exclude_computed_fields is only supported in Pydantic v2") return super().json( # type: ignore[reportDeprecated] indent=indent, include=include, From 9cae18c71078c8baae46bfb13298a8eadcfe5675 Mon Sep 17 00:00:00 2001 From: Dominic Saadi Date: Tue, 18 Nov 2025 19:27:10 -0800 Subject: [PATCH 10/45] fix(pydantic): ignore model extras in pydantic v2 (#156) * fix(pydantic): ignore model extras * only handle v2 for now --- src/browserbase/_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browserbase/_models.py b/src/browserbase/_models.py index ca9500b..bec6bc4 100644 --- a/src/browserbase/_models.py +++ b/src/browserbase/_models.py @@ -94,7 +94,7 @@ class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated] extra: Any = pydantic.Extra.allow # type: ignore else: model_config: ClassVar[ConfigDict] = ConfigDict( - extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) + extra="ignore", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true")) ) def to_dict( From ec88bf3f829ced4db3ee9bbaa249d0f447d0095d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 04:36:30 +0000 Subject: [PATCH 11/45] chore: add Python 3.14 classifier and testing --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bd2dc0d..2e107d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", From 588f8f480efaa914d5bbcb693dadd97936d66750 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 03:36:21 +0000 Subject: [PATCH 12/45] fix: ensure streams are always closed --- src/browserbase/_streaming.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/browserbase/_streaming.py b/src/browserbase/_streaming.py index 129714a..d107619 100644 --- a/src/browserbase/_streaming.py +++ b/src/browserbase/_streaming.py @@ -54,11 +54,12 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # As we might not fully consume the response stream, we need to close it explicitly - response.close() + try: + for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -117,11 +118,12 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # As we might not fully consume the response stream, we need to close it explicitly - await response.aclose() + try: + async for sse in iterator: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self From b0d4efb5945050ddfa449e468306e39aadeccf6a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 03:37:32 +0000 Subject: [PATCH 13/45] chore(deps): mypy 1.18.1 has a regression, pin to 1.17 --- pyproject.toml | 2 +- requirements-dev.lock | 4 +++- requirements.lock | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2e107d3..9487933 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ managed = true # version pins are in requirements-dev.lock dev-dependencies = [ "pyright==1.1.399", - "mypy", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", diff --git a/requirements-dev.lock b/requirements-dev.lock index 115b339..65d51ae 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -85,7 +85,7 @@ mdurl==0.1.2 multidict==6.6.4 # via aiohttp # via yarl -mypy==1.14.1 +mypy==1.17.0 mypy-extensions==1.0.0 # via mypy nodeenv==1.9.1 @@ -96,6 +96,8 @@ outcome==1.3.0.post0 packaging==24.1 # via nox # via pytest +pathspec==0.12.1 + # via mypy platformdirs==3.11.0 # via virtualenv playwright==1.48.0 diff --git a/requirements.lock b/requirements.lock index 55ea883..335d50f 100644 --- a/requirements.lock +++ b/requirements.lock @@ -55,21 +55,21 @@ multidict==6.4.4 propcache==0.3.1 # via aiohttp # via yarl -pydantic==2.11.9 +pydantic==2.12.5 # via browserbase -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic sniffio==1.3.0 # via anyio # via browserbase -typing-extensions==4.12.2 +typing-extensions==4.15.0 # via anyio # via browserbase # via multidict # via pydantic # via pydantic-core # via typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic yarl==1.20.0 # via aiohttp From 738e9be4acd99ad82e69ea876f1249948310f896 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:20:00 +0000 Subject: [PATCH 14/45] chore: update lockfile --- pyproject.toml | 14 ++++++++------ requirements-dev.lock | 15 +++++++++------ requirements.lock | 31 ++++++++++++++++--------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9487933..c092b26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,14 +7,16 @@ license = "Apache-2.0" authors = [ { name = "Browserbase", email = "support@browserbase.com" }, ] + dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.10, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", ] + requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", diff --git a/requirements-dev.lock b/requirements-dev.lock index 65d51ae..59545cb 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -44,7 +44,7 @@ distlib==0.3.9 # via virtualenv distro==1.9.0 # via browserbase -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio # via pytest # via trio @@ -86,7 +86,7 @@ multidict==6.6.4 # via aiohttp # via yarl mypy==1.17.0 -mypy-extensions==1.0.0 +mypy-extensions==1.1.0 # via mypy nodeenv==1.9.1 # via pyright @@ -98,7 +98,7 @@ packaging==24.1 # via pytest pathspec==0.12.1 # via mypy -platformdirs==3.11.0 +platformdirs==4.4.0 # via virtualenv playwright==1.48.0 # via pytest-playwright @@ -107,9 +107,9 @@ pluggy==1.5.0 propcache==0.3.2 # via aiohttp # via yarl -pydantic==2.11.9 +pydantic==2.12.5 # via browserbase -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic pyee==12.0.0 # via playwright @@ -163,14 +163,17 @@ typing-extensions==4.12.2 # via aiosignal # via anyio # via browserbase + # via exceptiongroup # via multidict # via mypy # via pydantic # via pydantic-core # via pyee # via pyright + # via pytest-asyncio # via typing-inspection -typing-inspection==0.4.1 + # via virtualenv +typing-inspection==0.4.2 # via pydantic # via rich # via selenium diff --git a/requirements.lock b/requirements.lock index 335d50f..188c9eb 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,28 +12,28 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.12.8 +aiohttp==3.13.2 # via browserbase # via httpx-aiohttp -aiosignal==1.3.2 +aiosignal==1.4.0 # via aiohttp -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +anyio==4.12.0 # via browserbase # via httpx async-timeout==5.0.1 # via aiohttp -attrs==25.3.0 +attrs==25.4.0 # via aiohttp -certifi==2023.7.22 +certifi==2025.11.12 # via httpcore # via httpx -distro==1.8.0 +distro==1.9.0 # via browserbase -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio -frozenlist==1.6.2 +frozenlist==1.8.0 # via aiohttp # via aiosignal h11==0.16.0 @@ -45,31 +45,32 @@ httpx==0.28.1 # via httpx-aiohttp httpx-aiohttp==0.1.9 # via browserbase -idna==3.4 +idna==3.11 # via anyio # via httpx # via yarl -multidict==6.4.4 +multidict==6.7.0 # via aiohttp # via yarl -propcache==0.3.1 +propcache==0.4.1 # via aiohttp # via yarl pydantic==2.12.5 # via browserbase pydantic-core==2.41.5 # via pydantic -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via browserbase typing-extensions==4.15.0 + # via aiosignal # via anyio # via browserbase + # via exceptiongroup # via multidict # via pydantic # via pydantic-core # via typing-inspection typing-inspection==0.4.2 # via pydantic -yarl==1.20.0 +yarl==1.22.0 # via aiohttp From 97a777f837a1f06940e05506e4b0237ace54cef8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 08:36:03 +0000 Subject: [PATCH 15/45] chore(docs): use environment variables for authentication in code snippets --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6edcd70..9c4cff6 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ pip install --pre browserbase[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python +import os import asyncio from browserbase import DefaultAioHttpClient from browserbase import AsyncBrowserbase @@ -103,7 +104,7 @@ from browserbase import AsyncBrowserbase async def main() -> None: async with AsyncBrowserbase( - api_key="My API Key", + api_key=os.environ.get("BROWSERBASE_API_KEY"), # This is the default and can be omitted http_client=DefaultAioHttpClient(), ) as client: session = await client.sessions.create( From 6e1371033109ed3a2519660bcc0da3f44f65f097 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:00:02 +0000 Subject: [PATCH 16/45] fix(types): allow pyright to infer TypedDict types within SequenceNotStr --- src/browserbase/_types.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browserbase/_types.py b/src/browserbase/_types.py index f86be54..0d54fc0 100644 --- a/src/browserbase/_types.py +++ b/src/browserbase/_types.py @@ -243,6 +243,9 @@ class HttpxSendArgs(TypedDict, total=False): if TYPE_CHECKING: # This works because str.__contains__ does not accept object (either in typeshed or at runtime) # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. class SequenceNotStr(Protocol[_T_co]): @overload def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... @@ -251,8 +254,6 @@ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... def __contains__(self, value: object, /) -> bool: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[_T_co]: ... - def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... - def count(self, value: Any, /) -> int: ... def __reversed__(self) -> Iterator[_T_co]: ... else: # just point this to a normal `Sequence` at runtime to avoid having to special case From 0332edf2964f32119e074d86baf3ef35e30f1b8d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 06:01:57 +0000 Subject: [PATCH 17/45] chore: add missing docstrings --- src/browserbase/types/session_create_params.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 3a517c0..2ba3640 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -85,6 +85,10 @@ class BrowserSettingsFingerprintScreen(TypedDict, total=False): class BrowserSettingsFingerprint(TypedDict, total=False): + """ + See usage examples [on the Stealth Mode page](/features/stealth-mode#fingerprinting) + """ + browsers: List[Literal["chrome", "edge", "firefox", "safari"]] devices: List[Literal["desktop", "mobile"]] @@ -160,6 +164,8 @@ class BrowserSettings(TypedDict, total=False): class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): + """Geographic location for the proxy. Optional.""" + country: Required[str] """Country code in ISO 3166-1 alpha-2 format""" From 851f268b6fbfa815f819bd3390d732ee26445b09 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 05:35:51 +0000 Subject: [PATCH 18/45] chore(internal): add missing files argument to base client --- src/browserbase/_base_client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/browserbase/_base_client.py b/src/browserbase/_base_client.py index 2485e4e..1fdbc92 100644 --- a/src/browserbase/_base_client.py +++ b/src/browserbase/_base_client.py @@ -1247,9 +1247,12 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1767,9 +1770,12 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return await self.request(cast_to, opts) async def put( From ca270852b7f1c3dd5544f73daeec8ddb41eac253 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 08:48:53 +0000 Subject: [PATCH 19/45] chore: speedup initial import --- src/browserbase/_client.py | 224 +++++++++++++++++++++++++++++-------- 1 file changed, 179 insertions(+), 45 deletions(-) diff --git a/src/browserbase/_client.py b/src/browserbase/_client.py index 8b54a5b..5bb997a 100644 --- a/src/browserbase/_client.py +++ b/src/browserbase/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Mapping +from typing import TYPE_CHECKING, Any, Mapping from typing_extensions import Self, override import httpx @@ -20,8 +20,8 @@ not_given, ) from ._utils import is_given, get_async_library +from ._compat import cached_property from ._version import __version__ -from .resources import contexts, projects, extensions from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, BrowserbaseError from ._base_client import ( @@ -29,7 +29,13 @@ SyncAPIClient, AsyncAPIClient, ) -from .resources.sessions import sessions + +if TYPE_CHECKING: + from .resources import contexts, projects, sessions, extensions + from .resources.contexts import ContextsResource, AsyncContextsResource + from .resources.projects import ProjectsResource, AsyncProjectsResource + from .resources.extensions import ExtensionsResource, AsyncExtensionsResource + from .resources.sessions.sessions import SessionsResource, AsyncSessionsResource __all__ = [ "Timeout", @@ -44,13 +50,6 @@ class Browserbase(SyncAPIClient): - contexts: contexts.ContextsResource - extensions: extensions.ExtensionsResource - projects: projects.ProjectsResource - sessions: sessions.SessionsResource - with_raw_response: BrowserbaseWithRawResponse - with_streaming_response: BrowserbaseWithStreamedResponse - # client options api_key: str @@ -105,12 +104,37 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.contexts = contexts.ContextsResource(self) - self.extensions = extensions.ExtensionsResource(self) - self.projects = projects.ProjectsResource(self) - self.sessions = sessions.SessionsResource(self) - self.with_raw_response = BrowserbaseWithRawResponse(self) - self.with_streaming_response = BrowserbaseWithStreamedResponse(self) + @cached_property + def contexts(self) -> ContextsResource: + from .resources.contexts import ContextsResource + + return ContextsResource(self) + + @cached_property + def extensions(self) -> ExtensionsResource: + from .resources.extensions import ExtensionsResource + + return ExtensionsResource(self) + + @cached_property + def projects(self) -> ProjectsResource: + from .resources.projects import ProjectsResource + + return ProjectsResource(self) + + @cached_property + def sessions(self) -> SessionsResource: + from .resources.sessions import SessionsResource + + return SessionsResource(self) + + @cached_property + def with_raw_response(self) -> BrowserbaseWithRawResponse: + return BrowserbaseWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BrowserbaseWithStreamedResponse: + return BrowserbaseWithStreamedResponse(self) @property @override @@ -218,13 +242,6 @@ def _make_status_error( class AsyncBrowserbase(AsyncAPIClient): - contexts: contexts.AsyncContextsResource - extensions: extensions.AsyncExtensionsResource - projects: projects.AsyncProjectsResource - sessions: sessions.AsyncSessionsResource - with_raw_response: AsyncBrowserbaseWithRawResponse - with_streaming_response: AsyncBrowserbaseWithStreamedResponse - # client options api_key: str @@ -279,12 +296,37 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.contexts = contexts.AsyncContextsResource(self) - self.extensions = extensions.AsyncExtensionsResource(self) - self.projects = projects.AsyncProjectsResource(self) - self.sessions = sessions.AsyncSessionsResource(self) - self.with_raw_response = AsyncBrowserbaseWithRawResponse(self) - self.with_streaming_response = AsyncBrowserbaseWithStreamedResponse(self) + @cached_property + def contexts(self) -> AsyncContextsResource: + from .resources.contexts import AsyncContextsResource + + return AsyncContextsResource(self) + + @cached_property + def extensions(self) -> AsyncExtensionsResource: + from .resources.extensions import AsyncExtensionsResource + + return AsyncExtensionsResource(self) + + @cached_property + def projects(self) -> AsyncProjectsResource: + from .resources.projects import AsyncProjectsResource + + return AsyncProjectsResource(self) + + @cached_property + def sessions(self) -> AsyncSessionsResource: + from .resources.sessions import AsyncSessionsResource + + return AsyncSessionsResource(self) + + @cached_property + def with_raw_response(self) -> AsyncBrowserbaseWithRawResponse: + return AsyncBrowserbaseWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBrowserbaseWithStreamedResponse: + return AsyncBrowserbaseWithStreamedResponse(self) @property @override @@ -392,35 +434,127 @@ def _make_status_error( class BrowserbaseWithRawResponse: + _client: Browserbase + def __init__(self, client: Browserbase) -> None: - self.contexts = contexts.ContextsResourceWithRawResponse(client.contexts) - self.extensions = extensions.ExtensionsResourceWithRawResponse(client.extensions) - self.projects = projects.ProjectsResourceWithRawResponse(client.projects) - self.sessions = sessions.SessionsResourceWithRawResponse(client.sessions) + self._client = client + + @cached_property + def contexts(self) -> contexts.ContextsResourceWithRawResponse: + from .resources.contexts import ContextsResourceWithRawResponse + + return ContextsResourceWithRawResponse(self._client.contexts) + + @cached_property + def extensions(self) -> extensions.ExtensionsResourceWithRawResponse: + from .resources.extensions import ExtensionsResourceWithRawResponse + + return ExtensionsResourceWithRawResponse(self._client.extensions) + + @cached_property + def projects(self) -> projects.ProjectsResourceWithRawResponse: + from .resources.projects import ProjectsResourceWithRawResponse + + return ProjectsResourceWithRawResponse(self._client.projects) + + @cached_property + def sessions(self) -> sessions.SessionsResourceWithRawResponse: + from .resources.sessions import SessionsResourceWithRawResponse + + return SessionsResourceWithRawResponse(self._client.sessions) class AsyncBrowserbaseWithRawResponse: + _client: AsyncBrowserbase + def __init__(self, client: AsyncBrowserbase) -> None: - self.contexts = contexts.AsyncContextsResourceWithRawResponse(client.contexts) - self.extensions = extensions.AsyncExtensionsResourceWithRawResponse(client.extensions) - self.projects = projects.AsyncProjectsResourceWithRawResponse(client.projects) - self.sessions = sessions.AsyncSessionsResourceWithRawResponse(client.sessions) + self._client = client + + @cached_property + def contexts(self) -> contexts.AsyncContextsResourceWithRawResponse: + from .resources.contexts import AsyncContextsResourceWithRawResponse + + return AsyncContextsResourceWithRawResponse(self._client.contexts) + + @cached_property + def extensions(self) -> extensions.AsyncExtensionsResourceWithRawResponse: + from .resources.extensions import AsyncExtensionsResourceWithRawResponse + + return AsyncExtensionsResourceWithRawResponse(self._client.extensions) + + @cached_property + def projects(self) -> projects.AsyncProjectsResourceWithRawResponse: + from .resources.projects import AsyncProjectsResourceWithRawResponse + + return AsyncProjectsResourceWithRawResponse(self._client.projects) + + @cached_property + def sessions(self) -> sessions.AsyncSessionsResourceWithRawResponse: + from .resources.sessions import AsyncSessionsResourceWithRawResponse + + return AsyncSessionsResourceWithRawResponse(self._client.sessions) class BrowserbaseWithStreamedResponse: + _client: Browserbase + def __init__(self, client: Browserbase) -> None: - self.contexts = contexts.ContextsResourceWithStreamingResponse(client.contexts) - self.extensions = extensions.ExtensionsResourceWithStreamingResponse(client.extensions) - self.projects = projects.ProjectsResourceWithStreamingResponse(client.projects) - self.sessions = sessions.SessionsResourceWithStreamingResponse(client.sessions) + self._client = client + + @cached_property + def contexts(self) -> contexts.ContextsResourceWithStreamingResponse: + from .resources.contexts import ContextsResourceWithStreamingResponse + + return ContextsResourceWithStreamingResponse(self._client.contexts) + + @cached_property + def extensions(self) -> extensions.ExtensionsResourceWithStreamingResponse: + from .resources.extensions import ExtensionsResourceWithStreamingResponse + + return ExtensionsResourceWithStreamingResponse(self._client.extensions) + + @cached_property + def projects(self) -> projects.ProjectsResourceWithStreamingResponse: + from .resources.projects import ProjectsResourceWithStreamingResponse + + return ProjectsResourceWithStreamingResponse(self._client.projects) + + @cached_property + def sessions(self) -> sessions.SessionsResourceWithStreamingResponse: + from .resources.sessions import SessionsResourceWithStreamingResponse + + return SessionsResourceWithStreamingResponse(self._client.sessions) class AsyncBrowserbaseWithStreamedResponse: + _client: AsyncBrowserbase + def __init__(self, client: AsyncBrowserbase) -> None: - self.contexts = contexts.AsyncContextsResourceWithStreamingResponse(client.contexts) - self.extensions = extensions.AsyncExtensionsResourceWithStreamingResponse(client.extensions) - self.projects = projects.AsyncProjectsResourceWithStreamingResponse(client.projects) - self.sessions = sessions.AsyncSessionsResourceWithStreamingResponse(client.sessions) + self._client = client + + @cached_property + def contexts(self) -> contexts.AsyncContextsResourceWithStreamingResponse: + from .resources.contexts import AsyncContextsResourceWithStreamingResponse + + return AsyncContextsResourceWithStreamingResponse(self._client.contexts) + + @cached_property + def extensions(self) -> extensions.AsyncExtensionsResourceWithStreamingResponse: + from .resources.extensions import AsyncExtensionsResourceWithStreamingResponse + + return AsyncExtensionsResourceWithStreamingResponse(self._client.extensions) + + @cached_property + def projects(self) -> projects.AsyncProjectsResourceWithStreamingResponse: + from .resources.projects import AsyncProjectsResourceWithStreamingResponse + + return AsyncProjectsResourceWithStreamingResponse(self._client.projects) + + @cached_property + def sessions(self) -> sessions.AsyncSessionsResourceWithStreamingResponse: + from .resources.sessions import AsyncSessionsResourceWithStreamingResponse + + return AsyncSessionsResourceWithStreamingResponse(self._client.sessions) Client = Browserbase From 4370dab0f116d5535259ee557c0dddf3fc006c80 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:33:56 +0000 Subject: [PATCH 20/45] fix: use async_to_httpx_files in patch method --- src/browserbase/_base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browserbase/_base_client.py b/src/browserbase/_base_client.py index 1fdbc92..eea3808 100644 --- a/src/browserbase/_base_client.py +++ b/src/browserbase/_base_client.py @@ -1774,7 +1774,7 @@ async def patch( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) From b96137ced74f55b77e904026ab76b8bf306a7543 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:03:23 +0000 Subject: [PATCH 21/45] chore(internal): add `--fix` argument to lint script --- scripts/lint | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/lint b/scripts/lint index feccbdd..7bc921a 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,8 +4,13 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running lints with --fix" + rye run fix:ruff +else + echo "==> Running lints" + rye run lint +fi echo "==> Making sure it imports" rye run python -c 'import browserbase' From 20dcbdc81a95efe53e2e57ff7b205e8a3c023bbc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:25:10 +0000 Subject: [PATCH 22/45] feat(api): api update --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 2cec9d4..9d3232f 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Browserbase + Copyright 2026 Browserbase Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 5dad0978043a3fe8d65c5ff06a064e2d9b40cda9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:19:07 +0000 Subject: [PATCH 23/45] chore(internal): codegen related update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9c4cff6..6863a76 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The REST API documentation can be found on [docs.browserbase.com](https://docs.b ```sh # install from PyPI -pip install --pre browserbase +pip install '--pre browserbase' ``` ## Usage @@ -90,7 +90,7 @@ You can enable this by installing `aiohttp`: ```sh # install from PyPI -pip install --pre browserbase[aiohttp] +pip install '--pre browserbase[aiohttp]' ``` Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: From 972c8370ed69360de2479cf0fc818be3df4679cf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:25:23 +0000 Subject: [PATCH 24/45] feat(client): add support for binary request streaming --- src/browserbase/_base_client.py | 145 +++++++++++++++++++++++-- src/browserbase/_models.py | 17 ++- src/browserbase/_types.py | 9 ++ tests/test_client.py | 187 +++++++++++++++++++++++++++++++- 4 files changed, 344 insertions(+), 14 deletions(-) diff --git a/src/browserbase/_base_client.py b/src/browserbase/_base_client.py index eea3808..6621d6c 100644 --- a/src/browserbase/_base_client.py +++ b/src/browserbase/_base_client.py @@ -9,6 +9,7 @@ import inspect import logging import platform +import warnings import email.utils from types import TracebackType from random import random @@ -51,9 +52,11 @@ ResponseT, AnyMapping, PostParser, + BinaryTypes, RequestFiles, HttpxSendArgs, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, not_given, @@ -477,8 +480,19 @@ def _build_request( retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -532,7 +546,13 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - if isinstance(json_data, bytes): + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): kwargs["content"] = json_data else: kwargs["json"] = json_data if is_given(json_data) else None @@ -1194,6 +1214,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1206,6 +1227,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1219,6 +1241,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1231,13 +1254,25 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) @@ -1247,11 +1282,23 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1261,11 +1308,23 @@ def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1275,9 +1334,19 @@ def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return self.request(cast_to, opts) def get_api_list( @@ -1717,6 +1786,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1729,6 +1799,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1742,6 +1813,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1754,13 +1826,25 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) @@ -1770,11 +1854,28 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, ) return await self.request(cast_to, opts) @@ -1784,11 +1885,23 @@ async def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) @@ -1798,9 +1911,19 @@ async def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return await self.request(cast_to, opts) def get_api_list( diff --git a/src/browserbase/_models.py b/src/browserbase/_models.py index bec6bc4..f6a9aaa 100644 --- a/src/browserbase/_models.py +++ b/src/browserbase/_models.py @@ -3,7 +3,20 @@ import os import inspect import weakref -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) from datetime import date, datetime from typing_extensions import ( List, @@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): timeout: float | Timeout | None files: HttpxRequestFiles | None idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] json_data: Body extra_json: AnyMapping follow_redirects: bool @@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel): post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() follow_redirects: Union[bool, None] = None + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None diff --git a/src/browserbase/_types.py b/src/browserbase/_types.py index 0d54fc0..abefae0 100644 --- a/src/browserbase/_types.py +++ b/src/browserbase/_types.py @@ -13,9 +13,11 @@ Mapping, TypeVar, Callable, + Iterable, Iterator, Optional, Sequence, + AsyncIterable, ) from typing_extensions import ( Set, @@ -56,6 +58,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, diff --git a/tests/test_client.py b/tests/test_client.py index db556fc..608cc1f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,10 +8,11 @@ import json import asyncio import inspect +import dataclasses import tracemalloc -from typing import Any, Union, cast +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -36,6 +37,7 @@ from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" @@ -50,6 +52,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 +def mirror_request_content(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, content=request.content) + + +# note: we can't use the httpx.MockTransport class as it consumes the request +# body itself, which means we can't test that the body is read lazily +class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): + def __init__( + self, + handler: Callable[[httpx.Request], httpx.Response] + | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + ) -> None: + self.handler = handler + + @override + def handle_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" + assert inspect.isfunction(self.handler), "handler must be a function" + return self.handler(request) + + @override + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" + return await self.handler(request) + + +@dataclasses.dataclass +class Counter: + value: int = 0 + + +def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + def _get_open_connections(client: Browserbase | AsyncBrowserbase) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -502,6 +555,70 @@ def test_multipart_repeating_array(self, client: Browserbase) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload(self, respx_mock: MockRouter, client: Browserbase) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + def test_binary_content_upload_with_iterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_sync_iterator([file_content], counter=counter) + + def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=request.read()) + + with Browserbase( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Browserbase) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) def test_basic_union_response(self, respx_mock: MockRouter, client: Browserbase) -> None: class Model1(BaseModel): @@ -1335,6 +1452,72 @@ def test_multipart_repeating_array(self, async_client: AsyncBrowserbase) -> None b"", ] + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = await async_client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + async def test_binary_content_upload_with_asynciterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_async_iterator([file_content], counter=counter) + + async def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=await request.aread()) + + async with AsyncBrowserbase( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncBrowserbase + ) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = await async_client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncBrowserbase) -> None: class Model1(BaseModel): From 7b01d951f1e4abaa345ee686d8b08c9066f183ca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:34:51 +0000 Subject: [PATCH 25/45] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8edf5a6..720a983 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/browserbase-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | @@ -44,7 +44,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/browserbase-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | @@ -81,7 +81,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/browserbase-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index b3c832c..7fb6d44 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Rye run: | diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 3e17e45..5beedb0 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'browserbase/sdk-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | From 9363c3dd4142917eca9e7f5872d68d73b0400a17 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 07:42:28 +0000 Subject: [PATCH 26/45] chore(ci): upgrade `actions/github-script` --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 720a983..c77d6d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: - name: Get GitHub OIDC Token if: github.repository == 'stainless-sdks/browserbase-python' id: github-oidc - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); From da8ce14deb91e1da722dbafa3f7e35bfd1fbd983 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 07:15:31 +0000 Subject: [PATCH 27/45] feat(client): add custom JSON encoder for extended type support --- src/browserbase/_base_client.py | 7 +- src/browserbase/_compat.py | 6 +- src/browserbase/_utils/_json.py | 35 +++++++++ tests/test_utils/test_json.py | 126 ++++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 src/browserbase/_utils/_json.py create mode 100644 tests/test_utils/test_json.py diff --git a/src/browserbase/_base_client.py b/src/browserbase/_base_client.py index 6621d6c..5bc9823 100644 --- a/src/browserbase/_base_client.py +++ b/src/browserbase/_base_client.py @@ -86,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps log: logging.Logger = logging.getLogger(__name__) @@ -554,8 +555,10 @@ def _build_request( kwargs["content"] = options.content elif isinstance(json_data, bytes): kwargs["content"] = json_data - else: - kwargs["json"] = json_data if is_given(json_data) else None + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/browserbase/_compat.py b/src/browserbase/_compat.py index bdef67f..786ff42 100644 --- a/src/browserbase/_compat.py +++ b/src/browserbase/_compat.py @@ -139,6 +139,7 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( @@ -148,13 +149,12 @@ def model_dump( exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) diff --git a/src/browserbase/_utils/_json.py b/src/browserbase/_utils/_json.py new file mode 100644 index 0000000..6058421 --- /dev/null +++ b/src/browserbase/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 0000000..9cf7b78 --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from browserbase import _compat +from browserbase._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' From b0c33068ffe4c033748a4ca7e4394cdd7c4c97be Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 07:56:05 +0000 Subject: [PATCH 28/45] chore(internal): bump dependencies --- requirements-dev.lock | 111 +++++++++++++++++++++++------------------- requirements.lock | 8 +-- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 59545cb..79e2e13 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,35 +12,40 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.12.15 +aiohttp==3.13.3 # via browserbase # via httpx-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.6.2.post1 +anyio==4.12.1 # via browserbase # via httpx -argcomplete==3.5.1 +argcomplete==3.6.3 # via nox async-timeout==5.0.1 # via aiohttp -attrs==24.2.0 +attrs==25.4.0 # via aiohttp + # via nox # via outcome # via trio -certifi==2024.8.30 +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2026.1.4 # via httpcore # via httpx # via requests # via selenium -charset-normalizer==3.4.0 +charset-normalizer==3.4.4 # via requests -colorlog==6.8.2 +colorlog==6.10.1 + # via nox +dependency-groups==1.3.1 # via nox -dirty-equals==0.8.0 -distlib==0.3.9 +dirty-equals==0.11 +distlib==0.4.0 # via virtualenv distro==1.9.0 # via browserbase @@ -49,117 +54,122 @@ exceptiongroup==1.3.1 # via pytest # via trio # via trio-websocket -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -filelock==3.16.1 +filelock==3.19.1 # via virtualenv -frozenlist==1.7.0 +frozenlist==1.8.0 # via aiohttp # via aiosignal -greenlet==3.1.1 +greenlet==3.2.5 # via playwright -h11==0.14.0 +h11==0.16.0 # via httpcore # via wsproto -httpcore==1.0.6 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via browserbase # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.9 +httpx-aiohttp==0.1.12 # via browserbase -idna==3.10 +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx # via requests # via trio # via yarl -importlib-metadata==8.5.0 -iniconfig==2.0.0 +importlib-metadata==8.7.1 +iniconfig==2.1.0 # via pytest markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -multidict==6.6.4 +multidict==6.7.0 # via aiohttp # via yarl mypy==1.17.0 mypy-extensions==1.1.0 # via mypy -nodeenv==1.9.1 +nodeenv==1.10.0 # via pyright -nox==2024.10.9 +nox==2025.11.12 outcome==1.3.0.post0 # via trio -packaging==24.1 + # via trio-websocket +packaging==25.0 + # via dependency-groups # via nox # via pytest -pathspec==0.12.1 +pathspec==1.0.3 # via mypy platformdirs==4.4.0 # via virtualenv -playwright==1.48.0 +playwright==1.58.0 # via pytest-playwright -pluggy==1.5.0 +pluggy==1.6.0 # via pytest -propcache==0.3.2 +propcache==0.4.1 # via aiohttp # via yarl pydantic==2.12.5 # via browserbase pydantic-core==2.41.5 # via pydantic -pyee==12.0.0 +pyee==13.0.1 # via playwright -pygments==2.18.0 +pygments==2.19.2 + # via pytest # via rich pyright==1.1.399 pysocks==1.7.1 # via urllib3 -pytest==8.3.3 +pytest==8.4.2 # via pytest-asyncio # via pytest-base-url # via pytest-playwright # via pytest-xdist -pytest-asyncio==0.24.0 +pytest-asyncio==1.2.0 pytest-base-url==2.1.0 # via pytest-playwright -pytest-playwright==0.5.2 +pytest-playwright==0.7.1 pytest-xdist==3.8.0 python-dateutil==2.9.0.post0 # via time-machine -python-dotenv==1.0.1 +python-dotenv==1.2.1 python-slugify==8.0.4 # via pytest-playwright -requests==2.32.3 +requests==2.32.5 # via pytest-base-url respx==0.22.0 -rich==13.9.3 -ruff==0.7.1 -selenium==4.25.0 -six==1.16.0 +rich==14.2.0 +ruff==0.14.13 +selenium==4.36.0 +six==1.17.0 # via python-dateutil sniffio==1.3.1 - # via anyio # via browserbase # via trio sortedcontainers==2.4.0 # via trio text-unidecode==1.3 # via python-slugify -time-machine==2.16.0 -tomli==2.0.2 +time-machine==2.19.0 +tomli==2.4.0 + # via dependency-groups # via mypy # via nox # via pytest -trio==0.27.0 +trio==0.31.0 # via selenium # via trio-websocket -trio-websocket==0.11.1 +trio-websocket==0.12.2 # via selenium -typing-extensions==4.12.2 +typing-extensions==4.15.0 # via aiosignal # via anyio # via browserbase @@ -171,22 +181,21 @@ typing-extensions==4.12.2 # via pyee # via pyright # via pytest-asyncio + # via selenium # via typing-inspection # via virtualenv typing-inspection==0.4.2 # via pydantic - # via rich - # via selenium -urllib3==2.2.3 +urllib3==2.6.3 # via requests # via selenium -virtualenv==20.24.5 +virtualenv==20.36.1 # via nox -websocket-client==1.8.0 +websocket-client==1.9.0 # via selenium wsproto==1.2.0 # via trio-websocket -yarl==1.20.1 +yarl==1.22.0 # via aiohttp -zipp==3.20.2 +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 188c9eb..ba67caa 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,21 +12,21 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via browserbase # via httpx-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.12.1 # via browserbase # via httpx async-timeout==5.0.1 # via aiohttp attrs==25.4.0 # via aiohttp -certifi==2025.11.12 +certifi==2026.1.4 # via httpcore # via httpx distro==1.9.0 @@ -43,7 +43,7 @@ httpcore==1.0.9 httpx==0.28.1 # via browserbase # via httpx-aiohttp -httpx-aiohttp==0.1.9 +httpx-aiohttp==0.1.12 # via browserbase idna==3.11 # via anyio From ba06a3e6d617c4b10be4874856006797fdec6e93 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:36:57 +0000 Subject: [PATCH 29/45] chore(internal): fix lint error on Python 3.14 --- src/browserbase/_utils/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browserbase/_utils/_compat.py b/src/browserbase/_utils/_compat.py index dd70323..2c70b29 100644 --- a/src/browserbase/_utils/_compat.py +++ b/src/browserbase/_utils/_compat.py @@ -26,7 +26,7 @@ def is_union(tp: Optional[Type[Any]]) -> bool: else: import types - return tp is Union or tp is types.UnionType + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] def is_typeddict(tp: Type[Any]) -> bool: From 36b3fc50c44221848b583e5d5f66e06e9f857a14 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 06:23:40 +0000 Subject: [PATCH 30/45] chore: format all `api.md` files --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c092b26..7e6634c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ format = { chain = [ # run formatting again to fix any inconsistencies when imports are stripped "format:ruff", ]} -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'" "format:ruff" = "ruff format" "lint" = { chain = [ From 9c928759fb88085da94df9921ef35663d1da926e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:38:24 +0000 Subject: [PATCH 31/45] chore: update mock server docs --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f8bfea..4f99ed6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh From f0ba58f620f3b30e4f275cce521de1d6e7fd7164 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:27:36 +0000 Subject: [PATCH 32/45] feat(api): manual updates --- .stats.yml | 4 +- README.md | 1 - src/browserbase/resources/contexts.py | 6 +- .../resources/sessions/sessions.py | 58 +++++++++---------- .../types/context_create_params.py | 4 +- .../types/session_create_params.py | 28 ++++++--- .../types/session_update_params.py | 12 ++-- tests/api_resources/test_contexts.py | 26 +++++---- tests/api_resources/test_sessions.py | 54 +++++++++-------- tests/test_client.py | 28 ++++----- 10 files changed, 112 insertions(+), 109 deletions(-) diff --git a/.stats.yml b/.stats.yml index b000c8c..766f657 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 18 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-be7a4aeebb1605262935b4b3ab446a95b1fad8a7d18098943dd548c8a486ef13.yml -openapi_spec_hash: 1c950a109f80140711e7ae2cf87fddad +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-43f4549d7362ccad24d8f6124e73023a6cd5b62dab9c48cf3173435ca2f23155.yml +openapi_spec_hash: 920add25d34d7a858dad464ea558ce48 config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b diff --git a/README.md b/README.md index 6863a76..b379eb7 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,6 @@ from browserbase import Browserbase client = Browserbase() session = client.sessions.create( - project_id="projectId", browser_settings={}, ) print(session.browser_settings) diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index d2bb416..d7561cf 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -5,7 +5,7 @@ import httpx from ..types import context_create_params -from .._types import Body, Query, Headers, NotGiven, not_given +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -46,7 +46,7 @@ def with_streaming_response(self) -> ContextsResourceWithStreamingResponse: def create( self, *, - project_id: str, + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -169,7 +169,7 @@ def with_streaming_response(self) -> AsyncContextsResourceWithStreamingResponse: async def create( self, *, - project_id: str, + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index ceaaeb8..6055430 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -99,10 +99,10 @@ def with_streaming_response(self) -> SessionsResourceWithStreamingResponse: def create( self, *, - project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, + project_id: str | Omit = omit, proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, @@ -117,17 +117,17 @@ def create( """Create a Session Args: - project_id: The Project ID. + extension_id: The uploaded Extension ID. - Can be found in - [Settings](https://www.browserbase.com/settings). - - extension_id: The uploaded Extension ID. See + See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -151,10 +151,10 @@ def create( "/v1/sessions", body=maybe_transform( { - "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, + "project_id": project_id, "proxies": proxies, "region": region, "api_timeout": api_timeout, @@ -205,8 +205,8 @@ def update( self, id: str, *, - project_id: str, status: Literal["REQUEST_RELEASE"], + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -214,17 +214,16 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionUpdateResponse: - """Update a Session + """ + Update a Session Args: - project_id: The Project ID. - - Can be found in - [Settings](https://www.browserbase.com/settings). - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -239,8 +238,8 @@ def update( f"/v1/sessions/{id}", body=maybe_transform( { - "project_id": project_id, "status": status, + "project_id": project_id, }, session_update_params.SessionUpdateParams, ), @@ -370,10 +369,10 @@ def with_streaming_response(self) -> AsyncSessionsResourceWithStreamingResponse: async def create( self, *, - project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, + project_id: str | Omit = omit, proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, @@ -388,17 +387,17 @@ async def create( """Create a Session Args: - project_id: The Project ID. + extension_id: The uploaded Extension ID. - Can be found in - [Settings](https://www.browserbase.com/settings). - - extension_id: The uploaded Extension ID. See + See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -422,10 +421,10 @@ async def create( "/v1/sessions", body=await async_maybe_transform( { - "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, + "project_id": project_id, "proxies": proxies, "region": region, "api_timeout": api_timeout, @@ -476,8 +475,8 @@ async def update( self, id: str, *, - project_id: str, status: Literal["REQUEST_RELEASE"], + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -485,17 +484,16 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionUpdateResponse: - """Update a Session + """ + Update a Session Args: - project_id: The Project ID. - - Can be found in - [Settings](https://www.browserbase.com/settings). - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -510,8 +508,8 @@ async def update( f"/v1/sessions/{id}", body=await async_maybe_transform( { - "project_id": project_id, "status": status, + "project_id": project_id, }, session_update_params.SessionUpdateParams, ), diff --git a/src/browserbase/types/context_create_params.py b/src/browserbase/types/context_create_params.py index 75cd1fc..66c6c46 100644 --- a/src/browserbase/types/context_create_params.py +++ b/src/browserbase/types/context_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -10,7 +10,7 @@ class ContextCreateParams(TypedDict, total=False): - project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] + project_id: Annotated[str, PropertyInfo(alias="projectId")] """The Project ID. Can be found in [Settings](https://www.browserbase.com/settings). diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 2ba3640..af594d9 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -19,16 +19,11 @@ "ProxiesUnionMember0UnionMember0", "ProxiesUnionMember0UnionMember0Geolocation", "ProxiesUnionMember0UnionMember1", + "ProxiesUnionMember0UnionMember2", ] class SessionCreateParams(TypedDict, total=False): - project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] - """The Project ID. - - Can be found in [Settings](https://www.browserbase.com/settings). - """ - browser_settings: Annotated[BrowserSettings, PropertyInfo(alias="browserSettings")] extension_id: Annotated[str, PropertyInfo(alias="extensionId")] @@ -43,6 +38,12 @@ class SessionCreateParams(TypedDict, total=False): Available on the Hobby Plan and above. """ + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """The Project ID. + + Can be found in [Settings](https://www.browserbase.com/settings). + """ + proxies: Union[Iterable[ProxiesUnionMember0], bool] """Proxy configuration. @@ -213,4 +214,17 @@ class ProxiesUnionMember0UnionMember1(TypedDict, total=False): """Username for external proxy authentication. Optional.""" -ProxiesUnionMember0: TypeAlias = Union[ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1] +class ProxiesUnionMember0UnionMember2(TypedDict, total=False): + type: Required[Literal["none"]] + """Type of proxy. Always 'none' for this config.""" + + domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] + """Domain pattern for which this proxy should be used. + + If omitted, defaults to all domains. Optional. + """ + + +ProxiesUnionMember0: TypeAlias = Union[ + ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1, ProxiesUnionMember0UnionMember2 +] diff --git a/src/browserbase/types/session_update_params.py b/src/browserbase/types/session_update_params.py index 66dcd35..71c589d 100644 --- a/src/browserbase/types/session_update_params.py +++ b/src/browserbase/types/session_update_params.py @@ -10,14 +10,14 @@ class SessionUpdateParams(TypedDict, total=False): - project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] - """The Project ID. - - Can be found in [Settings](https://www.browserbase.com/settings). - """ - status: Required[Literal["REQUEST_RELEASE"]] """Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges. """ + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """The Project ID. + + Can be found in [Settings](https://www.browserbase.com/settings). + """ diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 4ad2773..68f50df 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -23,6 +23,11 @@ class TestContexts: @parametrize def test_method_create(self, client: Browserbase) -> None: + context = client.contexts.create() + assert_matches_type(ContextCreateResponse, context, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Browserbase) -> None: context = client.contexts.create( project_id="projectId", ) @@ -30,9 +35,7 @@ def test_method_create(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.create( - project_id="projectId", - ) + response = client.contexts.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -41,9 +44,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.create( - project_id="projectId", - ) as response: + with client.contexts.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -136,6 +137,11 @@ class TestAsyncContexts: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: + context = await async_client.contexts.create() + assert_matches_type(ContextCreateResponse, context, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.create( project_id="projectId", ) @@ -143,9 +149,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.create( - project_id="projectId", - ) + response = await async_client.contexts.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -154,9 +158,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.create( - project_id="projectId", - ) as response: + async with async_client.contexts.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 7a16f64..7741a3f 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -25,15 +25,12 @@ class TestSessions: @parametrize def test_method_create(self, client: Browserbase) -> None: - session = client.sessions.create( - project_id="projectId", - ) + session = client.sessions.create() assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Browserbase) -> None: session = client.sessions.create( - project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -68,6 +65,7 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: }, extension_id="extensionId", keep_alive=True, + project_id="projectId", proxies=[ { "type": "browserbase", @@ -87,9 +85,7 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.sessions.with_raw_response.create( - project_id="projectId", - ) + response = client.sessions.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -98,9 +94,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.sessions.with_streaming_response.create( - project_id="projectId", - ) as response: + with client.sessions.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -151,16 +145,23 @@ def test_path_params_retrieve(self, client: Browserbase) -> None: def test_method_update(self, client: Browserbase) -> None: session = client.sessions.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) assert_matches_type(SessionUpdateResponse, session, path=["response"]) + @parametrize + def test_method_update_with_all_params(self, client: Browserbase) -> None: + session = client.sessions.update( + id="id", + status="REQUEST_RELEASE", + project_id="projectId", + ) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) + @parametrize def test_raw_response_update(self, client: Browserbase) -> None: response = client.sessions.with_raw_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) @@ -173,7 +174,6 @@ def test_raw_response_update(self, client: Browserbase) -> None: def test_streaming_response_update(self, client: Browserbase) -> None: with client.sessions.with_streaming_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed @@ -189,7 +189,6 @@ def test_path_params_update(self, client: Browserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.sessions.with_raw_response.update( id="", - project_id="projectId", status="REQUEST_RELEASE", ) @@ -272,15 +271,12 @@ class TestAsyncSessions: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: - session = await async_client.sessions.create( - project_id="projectId", - ) + session = await async_client.sessions.create() assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.create( - project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -315,6 +311,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas }, extension_id="extensionId", keep_alive=True, + project_id="projectId", proxies=[ { "type": "browserbase", @@ -334,9 +331,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.sessions.with_raw_response.create( - project_id="projectId", - ) + response = await async_client.sessions.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -345,9 +340,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.sessions.with_streaming_response.create( - project_id="projectId", - ) as response: + async with async_client.sessions.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -398,16 +391,23 @@ async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> Non async def test_method_update(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) assert_matches_type(SessionUpdateResponse, session, path=["response"]) + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncBrowserbase) -> None: + session = await async_client.sessions.update( + id="id", + status="REQUEST_RELEASE", + project_id="projectId", + ) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) + @parametrize async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None: response = await async_client.sessions.with_raw_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) @@ -420,7 +420,6 @@ async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None async def test_streaming_response_update(self, async_client: AsyncBrowserbase) -> None: async with async_client.sessions.with_streaming_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed @@ -436,7 +435,6 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.sessions.with_raw_response.update( id="", - project_id="projectId", status="REQUEST_RELEASE", ) diff --git a/tests/test_client.py b/tests/test_client.py index 608cc1f..71396d2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -864,7 +864,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.sessions.with_streaming_response.create(project_id="projectId").__enter__() + client.sessions.with_streaming_response.create().__enter__() assert _get_open_connections(client) == 0 @@ -874,7 +874,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.sessions.with_streaming_response.create(project_id="projectId").__enter__() + client.sessions.with_streaming_response.create().__enter__() assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -903,7 +903,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create(project_id="projectId") + response = client.sessions.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -927,9 +927,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -952,9 +950,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} - ) + response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1770,7 +1766,7 @@ async def test_retrying_timeout_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() + await async_client.sessions.with_streaming_response.create().__aenter__() assert _get_open_connections(async_client) == 0 @@ -1782,7 +1778,7 @@ async def test_retrying_status_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() + await async_client.sessions.with_streaming_response.create().__aenter__() assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1811,7 +1807,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create(project_id="projectId") + response = await client.sessions.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1835,9 +1831,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1860,9 +1854,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} - ) + response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From e281b8958b9a5b30ca90af0db36693a1eeaed8c5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:42:39 +0000 Subject: [PATCH 33/45] feat(api): manual updates --- .stats.yml | 4 +- api.md | 28 +++--- src/browserbase/resources/contexts.py | 18 ++-- src/browserbase/resources/extensions.py | 27 +++--- src/browserbase/resources/projects.py | 32 +++---- .../resources/sessions/sessions.py | 32 +++---- src/browserbase/types/__init__.py | 13 ++- ...ontext_retrieve_response.py => context.py} | 4 +- ...ension_create_response.py => extension.py} | 4 +- .../types/extension_retrieve_response.py | 22 ----- ...roject_retrieve_response.py => project.py} | 4 +- .../types/project_list_response.py | 27 +----- ...ect_usage_response.py => project_usage.py} | 4 +- ...{session_update_response.py => session.py} | 4 +- .../types/session_create_params.py | 86 +++++-------------- .../types/session_list_response.py | 58 ++----------- ...debug_response.py => session_live_urls.py} | 4 +- src/browserbase/types/sessions/__init__.py | 2 + .../types/sessions/log_list_response.py | 48 +---------- .../sessions/recording_retrieve_response.py | 26 +----- src/browserbase/types/sessions/session_log.py | 46 ++++++++++ .../types/sessions/session_recording.py | 24 ++++++ tests/api_resources/test_contexts.py | 18 ++-- tests/api_resources/test_extensions.py | 26 +++--- tests/api_resources/test_projects.py | 26 +++--- tests/api_resources/test_sessions.py | 82 ++++-------------- 26 files changed, 249 insertions(+), 420 deletions(-) rename src/browserbase/types/{context_retrieve_response.py => context.py} (84%) rename src/browserbase/types/{extension_create_response.py => extension.py} (85%) delete mode 100644 src/browserbase/types/extension_retrieve_response.py rename src/browserbase/types/{project_retrieve_response.py => project.py} (87%) rename src/browserbase/types/{project_usage_response.py => project_usage.py} (78%) rename src/browserbase/types/{session_update_response.py => session.py} (95%) rename src/browserbase/types/{session_debug_response.py => session_live_urls.py} (88%) create mode 100644 src/browserbase/types/sessions/session_log.py create mode 100644 src/browserbase/types/sessions/session_recording.py diff --git a/.stats.yml b/.stats.yml index 766f657..78eff16 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 18 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-43f4549d7362ccad24d8f6124e73023a6cd5b62dab9c48cf3173435ca2f23155.yml -openapi_spec_hash: 920add25d34d7a858dad464ea558ce48 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-b92143ddb16135de4ff65ce8bcdfe9991d11c73570f42f07ea27e0da86209a44.yml +openapi_spec_hash: 16eb6e6c9687f01d2a791775b27dc315 config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b diff --git a/api.md b/api.md index 0145485..dbb776f 100644 --- a/api.md +++ b/api.md @@ -3,13 +3,13 @@ Types: ```python -from browserbase.types import ContextCreateResponse, ContextRetrieveResponse, ContextUpdateResponse +from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse ``` Methods: - client.contexts.create(\*\*params) -> ContextCreateResponse -- client.contexts.retrieve(id) -> ContextRetrieveResponse +- client.contexts.retrieve(id) -> Context - client.contexts.update(id) -> ContextUpdateResponse # Extensions @@ -17,13 +17,13 @@ Methods: Types: ```python -from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse +from browserbase.types import Extension ``` Methods: -- client.extensions.create(\*\*params) -> ExtensionCreateResponse -- client.extensions.retrieve(id) -> ExtensionRetrieveResponse +- client.extensions.create(\*\*params) -> Extension +- client.extensions.retrieve(id) -> Extension - client.extensions.delete(id) -> None # Projects @@ -31,14 +31,14 @@ Methods: Types: ```python -from browserbase.types import ProjectRetrieveResponse, ProjectListResponse, ProjectUsageResponse +from browserbase.types import Project, ProjectUsage, ProjectListResponse ``` Methods: -- client.projects.retrieve(id) -> ProjectRetrieveResponse +- client.projects.retrieve(id) -> Project - client.projects.list() -> ProjectListResponse -- client.projects.usage(id) -> ProjectUsageResponse +- client.projects.usage(id) -> ProjectUsage # Sessions @@ -46,11 +46,11 @@ Types: ```python from browserbase.types import ( + Session, + SessionLiveURLs, SessionCreateResponse, SessionRetrieveResponse, - SessionUpdateResponse, SessionListResponse, - SessionDebugResponse, ) ``` @@ -58,9 +58,9 @@ Methods: - client.sessions.create(\*\*params) -> SessionCreateResponse - client.sessions.retrieve(id) -> SessionRetrieveResponse -- client.sessions.update(id, \*\*params) -> SessionUpdateResponse +- client.sessions.update(id, \*\*params) -> Session - client.sessions.list(\*\*params) -> SessionListResponse -- client.sessions.debug(id) -> SessionDebugResponse +- client.sessions.debug(id) -> SessionLiveURLs ## Downloads @@ -73,7 +73,7 @@ Methods: Types: ```python -from browserbase.types.sessions import LogListResponse +from browserbase.types.sessions import SessionLog, LogListResponse ``` Methods: @@ -85,7 +85,7 @@ Methods: Types: ```python -from browserbase.types.sessions import RecordingRetrieveResponse +from browserbase.types.sessions import SessionRecording, RecordingRetrieveResponse ``` Methods: diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index d7561cf..4379aa9 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -16,9 +16,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.context import Context from ..types.context_create_response import ContextCreateResponse from ..types.context_update_response import ContextUpdateResponse -from ..types.context_retrieve_response import ContextRetrieveResponse __all__ = ["ContextsResource", "AsyncContextsResource"] @@ -89,9 +89,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ContextRetrieveResponse: + ) -> Context: """ - Get a Context + Context Args: extra_headers: Send extra headers @@ -109,7 +109,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ContextRetrieveResponse, + cast_to=Context, ) def update( @@ -124,7 +124,7 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update a Context + Update Context Args: extra_headers: Send extra headers @@ -212,9 +212,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ContextRetrieveResponse: + ) -> Context: """ - Get a Context + Context Args: extra_headers: Send extra headers @@ -232,7 +232,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ContextRetrieveResponse, + cast_to=Context, ) async def update( @@ -247,7 +247,7 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update a Context + Update Context Args: extra_headers: Send extra headers diff --git a/src/browserbase/resources/extensions.py b/src/browserbase/resources/extensions.py index 21d06e7..882a495 100644 --- a/src/browserbase/resources/extensions.py +++ b/src/browserbase/resources/extensions.py @@ -18,8 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.extension_create_response import ExtensionCreateResponse -from ..types.extension_retrieve_response import ExtensionRetrieveResponse +from ..types.extension import Extension __all__ = ["ExtensionsResource", "AsyncExtensionsResource"] @@ -54,7 +53,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionCreateResponse: + ) -> Extension: """ Upload an Extension @@ -80,7 +79,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionCreateResponse, + cast_to=Extension, ) def retrieve( @@ -93,9 +92,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionRetrieveResponse: + ) -> Extension: """ - Get an Extension + Extension Args: extra_headers: Send extra headers @@ -113,7 +112,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionRetrieveResponse, + cast_to=Extension, ) def delete( @@ -128,7 +127,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete an Extension + Delete Extension Args: extra_headers: Send extra headers @@ -181,7 +180,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionCreateResponse: + ) -> Extension: """ Upload an Extension @@ -207,7 +206,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionCreateResponse, + cast_to=Extension, ) async def retrieve( @@ -220,9 +219,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionRetrieveResponse: + ) -> Extension: """ - Get an Extension + Extension Args: extra_headers: Send extra headers @@ -240,7 +239,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionRetrieveResponse, + cast_to=Extension, ) async def delete( @@ -255,7 +254,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete an Extension + Delete Extension Args: extra_headers: Send extra headers diff --git a/src/browserbase/resources/projects.py b/src/browserbase/resources/projects.py index 62c28af..a6ae633 100644 --- a/src/browserbase/resources/projects.py +++ b/src/browserbase/resources/projects.py @@ -14,9 +14,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.project import Project +from ..types.project_usage import ProjectUsage from ..types.project_list_response import ProjectListResponse -from ..types.project_usage_response import ProjectUsageResponse -from ..types.project_retrieve_response import ProjectRetrieveResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -51,9 +51,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectRetrieveResponse: + ) -> Project: """ - Get a Project + Project Args: extra_headers: Send extra headers @@ -71,7 +71,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectRetrieveResponse, + cast_to=Project, ) def list( @@ -84,7 +84,7 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List Projects""" + """List projects""" return self._get( "/v1/projects", options=make_request_options( @@ -103,9 +103,9 @@ def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsageResponse: + ) -> ProjectUsage: """ - Get Project Usage + Project Usage Args: extra_headers: Send extra headers @@ -123,7 +123,7 @@ def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsageResponse, + cast_to=ProjectUsage, ) @@ -157,9 +157,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectRetrieveResponse: + ) -> Project: """ - Get a Project + Project Args: extra_headers: Send extra headers @@ -177,7 +177,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectRetrieveResponse, + cast_to=Project, ) async def list( @@ -190,7 +190,7 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List Projects""" + """List projects""" return await self._get( "/v1/projects", options=make_request_options( @@ -209,9 +209,9 @@ async def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsageResponse: + ) -> ProjectUsage: """ - Get Project Usage + Project Usage Args: extra_headers: Send extra headers @@ -229,7 +229,7 @@ async def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsageResponse, + cast_to=ProjectUsage, ) diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 6055430..09fd15a 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -51,10 +51,10 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options +from ...types.session import Session +from ...types.session_live_urls import SessionLiveURLs from ...types.session_list_response import SessionListResponse -from ...types.session_debug_response import SessionDebugResponse from ...types.session_create_response import SessionCreateResponse -from ...types.session_update_response import SessionUpdateResponse from ...types.session_retrieve_response import SessionRetrieveResponse __all__ = ["SessionsResource", "AsyncSessionsResource"] @@ -103,7 +103,7 @@ def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -180,7 +180,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Get a Session + Session Args: extra_headers: Send extra headers @@ -213,9 +213,9 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionUpdateResponse: + ) -> Session: """ - Update a Session + Update Session Args: status: Set to `REQUEST_RELEASE` to request that the session complete. Use before @@ -246,7 +246,7 @@ def update( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionUpdateResponse, + cast_to=Session, ) def list( @@ -306,7 +306,7 @@ def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionDebugResponse: + ) -> SessionLiveURLs: """ Session Live URLs @@ -326,7 +326,7 @@ def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionDebugResponse, + cast_to=SessionLiveURLs, ) @@ -373,7 +373,7 @@ async def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -450,7 +450,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Get a Session + Session Args: extra_headers: Send extra headers @@ -483,9 +483,9 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionUpdateResponse: + ) -> Session: """ - Update a Session + Update Session Args: status: Set to `REQUEST_RELEASE` to request that the session complete. Use before @@ -516,7 +516,7 @@ async def update( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionUpdateResponse, + cast_to=Session, ) async def list( @@ -576,7 +576,7 @@ async def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionDebugResponse: + ) -> SessionLiveURLs: """ Session Live URLs @@ -596,7 +596,7 @@ async def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionDebugResponse, + cast_to=SessionLiveURLs, ) diff --git a/src/browserbase/types/__init__.py b/src/browserbase/types/__init__.py index 20e2f90..4dd85dd 100644 --- a/src/browserbase/types/__init__.py +++ b/src/browserbase/types/__init__.py @@ -2,21 +2,20 @@ from __future__ import annotations +from .context import Context as Context +from .project import Project as Project +from .session import Session as Session +from .extension import Extension as Extension +from .project_usage import ProjectUsage as ProjectUsage +from .session_live_urls import SessionLiveURLs as SessionLiveURLs from .session_list_params import SessionListParams as SessionListParams from .context_create_params import ContextCreateParams as ContextCreateParams from .project_list_response import ProjectListResponse as ProjectListResponse from .session_create_params import SessionCreateParams as SessionCreateParams from .session_list_response import SessionListResponse as SessionListResponse from .session_update_params import SessionUpdateParams as SessionUpdateParams -from .project_usage_response import ProjectUsageResponse as ProjectUsageResponse -from .session_debug_response import SessionDebugResponse as SessionDebugResponse from .context_create_response import ContextCreateResponse as ContextCreateResponse from .context_update_response import ContextUpdateResponse as ContextUpdateResponse from .extension_create_params import ExtensionCreateParams as ExtensionCreateParams from .session_create_response import SessionCreateResponse as SessionCreateResponse -from .session_update_response import SessionUpdateResponse as SessionUpdateResponse -from .context_retrieve_response import ContextRetrieveResponse as ContextRetrieveResponse -from .extension_create_response import ExtensionCreateResponse as ExtensionCreateResponse -from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .session_retrieve_response import SessionRetrieveResponse as SessionRetrieveResponse -from .extension_retrieve_response import ExtensionRetrieveResponse as ExtensionRetrieveResponse diff --git a/src/browserbase/types/context_retrieve_response.py b/src/browserbase/types/context.py similarity index 84% rename from src/browserbase/types/context_retrieve_response.py rename to src/browserbase/types/context.py index c2cd692..cb5c32f 100644 --- a/src/browserbase/types/context_retrieve_response.py +++ b/src/browserbase/types/context.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["ContextRetrieveResponse"] +__all__ = ["Context"] -class ContextRetrieveResponse(BaseModel): +class Context(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension_create_response.py b/src/browserbase/types/extension.py similarity index 85% rename from src/browserbase/types/extension_create_response.py rename to src/browserbase/types/extension.py index d2b74f4..94582c3 100644 --- a/src/browserbase/types/extension_create_response.py +++ b/src/browserbase/types/extension.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["ExtensionCreateResponse"] +__all__ = ["Extension"] -class ExtensionCreateResponse(BaseModel): +class Extension(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension_retrieve_response.py b/src/browserbase/types/extension_retrieve_response.py deleted file mode 100644 index c786348..0000000 --- a/src/browserbase/types/extension_retrieve_response.py +++ /dev/null @@ -1,22 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["ExtensionRetrieveResponse"] - - -class ExtensionRetrieveResponse(BaseModel): - id: str - - created_at: datetime = FieldInfo(alias="createdAt") - - file_name: str = FieldInfo(alias="fileName") - - project_id: str = FieldInfo(alias="projectId") - """The Project ID linked to the uploaded Extension.""" - - updated_at: datetime = FieldInfo(alias="updatedAt") diff --git a/src/browserbase/types/project_retrieve_response.py b/src/browserbase/types/project.py similarity index 87% rename from src/browserbase/types/project_retrieve_response.py rename to src/browserbase/types/project.py index 7812667..dc3cf33 100644 --- a/src/browserbase/types/project_retrieve_response.py +++ b/src/browserbase/types/project.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["ProjectRetrieveResponse"] +__all__ = ["Project"] -class ProjectRetrieveResponse(BaseModel): +class Project(BaseModel): id: str concurrency: int diff --git a/src/browserbase/types/project_list_response.py b/src/browserbase/types/project_list_response.py index e364b52..2d05a23 100644 --- a/src/browserbase/types/project_list_response.py +++ b/src/browserbase/types/project_list_response.py @@ -1,31 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List -from datetime import datetime from typing_extensions import TypeAlias -from pydantic import Field as FieldInfo +from .project import Project -from .._models import BaseModel +__all__ = ["ProjectListResponse"] -__all__ = ["ProjectListResponse", "ProjectListResponseItem"] - - -class ProjectListResponseItem(BaseModel): - id: str - - concurrency: int - """The maximum number of sessions that this project can run concurrently.""" - - created_at: datetime = FieldInfo(alias="createdAt") - - default_timeout: int = FieldInfo(alias="defaultTimeout") - - name: str - - owner_id: str = FieldInfo(alias="ownerId") - - updated_at: datetime = FieldInfo(alias="updatedAt") - - -ProjectListResponse: TypeAlias = List[ProjectListResponseItem] +ProjectListResponse: TypeAlias = List[Project] diff --git a/src/browserbase/types/project_usage_response.py b/src/browserbase/types/project_usage.py similarity index 78% rename from src/browserbase/types/project_usage_response.py rename to src/browserbase/types/project_usage.py index b52fccf..c8a03f5 100644 --- a/src/browserbase/types/project_usage_response.py +++ b/src/browserbase/types/project_usage.py @@ -4,10 +4,10 @@ from .._models import BaseModel -__all__ = ["ProjectUsageResponse"] +__all__ = ["ProjectUsage"] -class ProjectUsageResponse(BaseModel): +class ProjectUsage(BaseModel): browser_minutes: int = FieldInfo(alias="browserMinutes") proxy_bytes: int = FieldInfo(alias="proxyBytes") diff --git a/src/browserbase/types/session_update_response.py b/src/browserbase/types/session.py similarity index 95% rename from src/browserbase/types/session_update_response.py rename to src/browserbase/types/session.py index 67a1371..16450e2 100644 --- a/src/browserbase/types/session_update_response.py +++ b/src/browserbase/types/session.py @@ -8,10 +8,10 @@ from .._models import BaseModel -__all__ = ["SessionUpdateResponse"] +__all__ = ["Session"] -class SessionUpdateResponse(BaseModel): +class Session(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index af594d9..33dd6fd 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -2,24 +2,21 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable +from typing import Dict, Union, Iterable from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict -from .._types import SequenceNotStr from .._utils import PropertyInfo __all__ = [ "SessionCreateParams", "BrowserSettings", "BrowserSettingsContext", - "BrowserSettingsFingerprint", - "BrowserSettingsFingerprintScreen", "BrowserSettingsViewport", - "ProxiesUnionMember0", - "ProxiesUnionMember0UnionMember0", - "ProxiesUnionMember0UnionMember0Geolocation", - "ProxiesUnionMember0UnionMember1", - "ProxiesUnionMember0UnionMember2", + "ProxiesUnionMember1", + "ProxiesUnionMember1BrowserbaseProxyConfig", + "ProxiesUnionMember1BrowserbaseProxyConfigGeolocation", + "ProxiesUnionMember1ExternalProxyConfig", + "ProxiesUnionMember1NoneProxyConfig", ] @@ -44,7 +41,7 @@ class SessionCreateParams(TypedDict, total=False): Can be found in [Settings](https://www.browserbase.com/settings). """ - proxies: Union[Iterable[ProxiesUnionMember0], bool] + proxies: Union[bool, Iterable[ProxiesUnionMember1]] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -75,42 +72,10 @@ class BrowserSettingsContext(TypedDict, total=False): """Whether or not to persist the context after browsing. Defaults to `false`.""" -class BrowserSettingsFingerprintScreen(TypedDict, total=False): - max_height: Annotated[int, PropertyInfo(alias="maxHeight")] - - max_width: Annotated[int, PropertyInfo(alias="maxWidth")] - - min_height: Annotated[int, PropertyInfo(alias="minHeight")] - - min_width: Annotated[int, PropertyInfo(alias="minWidth")] - - -class BrowserSettingsFingerprint(TypedDict, total=False): - """ - See usage examples [on the Stealth Mode page](/features/stealth-mode#fingerprinting) - """ - - browsers: List[Literal["chrome", "edge", "firefox", "safari"]] - - devices: List[Literal["desktop", "mobile"]] - - http_version: Annotated[Literal["1", "2"], PropertyInfo(alias="httpVersion")] - - locales: SequenceNotStr[str] - - operating_systems: Annotated[ - List[Literal["android", "ios", "linux", "macos", "windows"]], PropertyInfo(alias="operatingSystems") - ] - - screen: BrowserSettingsFingerprintScreen - - class BrowserSettingsViewport(TypedDict, total=False): height: int - """The height of the browser.""" width: int - """The width of the browser.""" class BrowserSettings(TypedDict, total=False): @@ -140,12 +105,6 @@ class BrowserSettings(TypedDict, total=False): See [Upload Extension](/reference/api/upload-an-extension). """ - fingerprint: BrowserSettingsFingerprint - """ - See usage examples - [on the Stealth Mode page](/features/stealth-mode#fingerprinting) - """ - log_session: Annotated[bool, PropertyInfo(alias="logSession")] """Enable or disable session logging. Defaults to `true`.""" @@ -164,8 +123,8 @@ class BrowserSettings(TypedDict, total=False): viewport: BrowserSettingsViewport -class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): - """Geographic location for the proxy. Optional.""" +class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): + """Configuration for geolocation""" country: Required[str] """Country code in ISO 3166-1 alpha-2 format""" @@ -177,7 +136,7 @@ class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): """US state code (2 characters). Must also specify US as the country. Optional.""" -class ProxiesUnionMember0UnionMember0(TypedDict, total=False): +class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): type: Required[Literal["browserbase"]] """Type of proxy. @@ -190,11 +149,11 @@ class ProxiesUnionMember0UnionMember0(TypedDict, total=False): If omitted, defaults to all domains. Optional. """ - geolocation: ProxiesUnionMember0UnionMember0Geolocation - """Geographic location for the proxy. Optional.""" + geolocation: ProxiesUnionMember1BrowserbaseProxyConfigGeolocation + """Configuration for geolocation""" -class ProxiesUnionMember0UnionMember1(TypedDict, total=False): +class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): server: Required[str] """Server URL for external proxy. Required.""" @@ -214,17 +173,16 @@ class ProxiesUnionMember0UnionMember1(TypedDict, total=False): """Username for external proxy authentication. Optional.""" -class ProxiesUnionMember0UnionMember2(TypedDict, total=False): - type: Required[Literal["none"]] - """Type of proxy. Always 'none' for this config.""" - - domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] - """Domain pattern for which this proxy should be used. +class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): + domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] + """Domain pattern for which site should have proxies disabled.""" - If omitted, defaults to all domains. Optional. - """ + type: Required[Literal["none"]] + """Type of proxy. Use 'none' to disable proxy for matching domains.""" -ProxiesUnionMember0: TypeAlias = Union[ - ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1, ProxiesUnionMember0UnionMember2 +ProxiesUnionMember1: TypeAlias = Union[ + ProxiesUnionMember1BrowserbaseProxyConfig, + ProxiesUnionMember1ExternalProxyConfig, + ProxiesUnionMember1NoneProxyConfig, ] diff --git a/src/browserbase/types/session_list_response.py b/src/browserbase/types/session_list_response.py index 4c1bd88..ca162dd 100644 --- a/src/browserbase/types/session_list_response.py +++ b/src/browserbase/types/session_list_response.py @@ -1,58 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional -from datetime import datetime -from typing_extensions import Literal, TypeAlias +from typing import List +from typing_extensions import TypeAlias -from pydantic import Field as FieldInfo +from .session import Session -from .._models import BaseModel +__all__ = ["SessionListResponse"] -__all__ = ["SessionListResponse", "SessionListResponseItem"] - - -class SessionListResponseItem(BaseModel): - id: str - - created_at: datetime = FieldInfo(alias="createdAt") - - expires_at: datetime = FieldInfo(alias="expiresAt") - - keep_alive: bool = FieldInfo(alias="keepAlive") - """Indicates if the Session was created to be kept alive upon disconnections""" - - project_id: str = FieldInfo(alias="projectId") - """The Project ID linked to the Session.""" - - proxy_bytes: int = FieldInfo(alias="proxyBytes") - """Bytes used via the [Proxy](/features/stealth-mode#proxies-and-residential-ips)""" - - region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] - """The region where the Session is running.""" - - started_at: datetime = FieldInfo(alias="startedAt") - - status: Literal["RUNNING", "ERROR", "TIMED_OUT", "COMPLETED"] - - updated_at: datetime = FieldInfo(alias="updatedAt") - - avg_cpu_usage: Optional[int] = FieldInfo(alias="avgCpuUsage", default=None) - """CPU used by the Session""" - - context_id: Optional[str] = FieldInfo(alias="contextId", default=None) - """Optional. The Context linked to the Session.""" - - ended_at: Optional[datetime] = FieldInfo(alias="endedAt", default=None) - - memory_usage: Optional[int] = FieldInfo(alias="memoryUsage", default=None) - """Memory used by the Session""" - - user_metadata: Optional[Dict[str, object]] = FieldInfo(alias="userMetadata", default=None) - """Arbitrary user metadata to attach to the session. - - To learn more about user metadata, see - [User Metadata](/features/sessions#user-metadata). - """ - - -SessionListResponse: TypeAlias = List[SessionListResponseItem] +SessionListResponse: TypeAlias = List[Session] diff --git a/src/browserbase/types/session_debug_response.py b/src/browserbase/types/session_live_urls.py similarity index 88% rename from src/browserbase/types/session_debug_response.py rename to src/browserbase/types/session_live_urls.py index 9cee7a7..3c7ba32 100644 --- a/src/browserbase/types/session_debug_response.py +++ b/src/browserbase/types/session_live_urls.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["SessionDebugResponse", "Page"] +__all__ = ["SessionLiveURLs", "Page"] class Page(BaseModel): @@ -23,7 +23,7 @@ class Page(BaseModel): url: str -class SessionDebugResponse(BaseModel): +class SessionLiveURLs(BaseModel): debugger_fullscreen_url: str = FieldInfo(alias="debuggerFullscreenUrl") debugger_url: str = FieldInfo(alias="debuggerUrl") diff --git a/src/browserbase/types/sessions/__init__.py b/src/browserbase/types/sessions/__init__.py index 69d5470..0cef6b1 100644 --- a/src/browserbase/types/sessions/__init__.py +++ b/src/browserbase/types/sessions/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations +from .session_log import SessionLog as SessionLog from .log_list_response import LogListResponse as LogListResponse +from .session_recording import SessionRecording as SessionRecording from .upload_create_params import UploadCreateParams as UploadCreateParams from .upload_create_response import UploadCreateResponse as UploadCreateResponse from .recording_retrieve_response import RecordingRetrieveResponse as RecordingRetrieveResponse diff --git a/src/browserbase/types/sessions/log_list_response.py b/src/browserbase/types/sessions/log_list_response.py index efd848a..2b325a8 100644 --- a/src/browserbase/types/sessions/log_list_response.py +++ b/src/browserbase/types/sessions/log_list_response.py @@ -1,50 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import List from typing_extensions import TypeAlias -from pydantic import Field as FieldInfo +from .session_log import SessionLog -from ..._models import BaseModel +__all__ = ["LogListResponse"] -__all__ = ["LogListResponse", "LogListResponseItem", "LogListResponseItemRequest", "LogListResponseItemResponse"] - - -class LogListResponseItemRequest(BaseModel): - params: Dict[str, object] - - raw_body: str = FieldInfo(alias="rawBody") - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class LogListResponseItemResponse(BaseModel): - raw_body: str = FieldInfo(alias="rawBody") - - result: Dict[str, object] - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class LogListResponseItem(BaseModel): - method: str - - page_id: int = FieldInfo(alias="pageId") - - session_id: str = FieldInfo(alias="sessionId") - - frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) - - loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) - - request: Optional[LogListResponseItemRequest] = None - - response: Optional[LogListResponseItemResponse] = None - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -LogListResponse: TypeAlias = List[LogListResponseItem] +LogListResponse: TypeAlias = List[SessionLog] diff --git a/src/browserbase/types/sessions/recording_retrieve_response.py b/src/browserbase/types/sessions/recording_retrieve_response.py index d3613b8..951969b 100644 --- a/src/browserbase/types/sessions/recording_retrieve_response.py +++ b/src/browserbase/types/sessions/recording_retrieve_response.py @@ -1,28 +1,10 @@ # 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 -from pydantic import Field as FieldInfo +from .session_recording import SessionRecording -from ..._models import BaseModel +__all__ = ["RecordingRetrieveResponse"] -__all__ = ["RecordingRetrieveResponse", "RecordingRetrieveResponseItem"] - - -class RecordingRetrieveResponseItem(BaseModel): - data: Dict[str, object] - """ - See - [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). - """ - - session_id: str = FieldInfo(alias="sessionId") - - timestamp: int - """milliseconds that have elapsed since the UNIX epoch""" - - type: int - - -RecordingRetrieveResponse: TypeAlias = List[RecordingRetrieveResponseItem] +RecordingRetrieveResponse: TypeAlias = List[SessionRecording] diff --git a/src/browserbase/types/sessions/session_log.py b/src/browserbase/types/sessions/session_log.py new file mode 100644 index 0000000..428f518 --- /dev/null +++ b/src/browserbase/types/sessions/session_log.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["SessionLog", "Request", "Response"] + + +class Request(BaseModel): + params: Dict[str, object] + + raw_body: str = FieldInfo(alias="rawBody") + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class Response(BaseModel): + raw_body: str = FieldInfo(alias="rawBody") + + result: Dict[str, object] + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class SessionLog(BaseModel): + method: str + + page_id: int = FieldInfo(alias="pageId") + + session_id: str = FieldInfo(alias="sessionId") + + frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) + + loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) + + request: Optional[Request] = None + + response: Optional[Response] = None + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" diff --git a/src/browserbase/types/sessions/session_recording.py b/src/browserbase/types/sessions/session_recording.py new file mode 100644 index 0000000..c847137 --- /dev/null +++ b/src/browserbase/types/sessions/session_recording.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["SessionRecording"] + + +class SessionRecording(BaseModel): + data: Dict[str, object] + """ + See + [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). + """ + + session_id: str = FieldInfo(alias="sessionId") + + timestamp: int + """milliseconds that have elapsed since the UNIX epoch""" + + type: int diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 68f50df..83267ca 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -9,11 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import ( - ContextCreateResponse, - ContextUpdateResponse, - ContextRetrieveResponse, -) +from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -58,7 +54,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: context = client.contexts.retrieve( "id", ) - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -69,7 +65,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -80,7 +76,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -172,7 +168,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.retrieve( "id", ) - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -183,7 +179,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -194,7 +190,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py index e32ae9b..6b6a018 100644 --- a/tests/api_resources/test_extensions.py +++ b/tests/api_resources/test_extensions.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse +from browserbase.types import Extension base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_create(self, client: Browserbase) -> None: extension = client.extensions.create( file=b"raw file contents", ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_raw_response_create(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_create(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: extension = client.extensions.retrieve( "id", ) - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -64,7 +64,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -75,7 +75,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -135,7 +135,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.create( file=b"raw file contents", ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -146,7 +146,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -157,7 +157,7 @@ async def test_streaming_response_create(self, async_client: AsyncBrowserbase) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -166,7 +166,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.retrieve( "id", ) - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -177,7 +177,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -188,7 +188,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 0d8e3c9..c8241bf 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import ProjectListResponse, ProjectUsageResponse, ProjectRetrieveResponse +from browserbase.types import Project, ProjectUsage, ProjectListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: project = client.projects.retrieve( "id", ) - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -85,7 +85,7 @@ def test_method_usage(self, client: Browserbase) -> None: project = client.projects.usage( "id", ) - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize def test_raw_response_usage(self, client: Browserbase) -> None: @@ -96,7 +96,7 @@ def test_raw_response_usage(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize def test_streaming_response_usage(self, client: Browserbase) -> None: @@ -107,7 +107,7 @@ def test_streaming_response_usage(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -129,7 +129,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.retrieve( "id", ) - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -140,7 +140,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -151,7 +151,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -192,7 +192,7 @@ async def test_method_usage(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.usage( "id", ) - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -203,7 +203,7 @@ async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -214,7 +214,7 @@ async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 7741a3f..6a21aa8 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -10,10 +10,10 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type from browserbase.types import ( + Session, + SessionLiveURLs, SessionListResponse, - SessionDebugResponse, SessionCreateResponse, - SessionUpdateResponse, SessionRetrieveResponse, ) @@ -41,19 +41,6 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: "persist": True, }, "extension_id": "extensionId", - "fingerprint": { - "browsers": ["chrome"], - "devices": ["desktop"], - "http_version": "1", - "locales": ["string"], - "operating_systems": ["android"], - "screen": { - "max_height": 0, - "max_width": 0, - "min_height": 0, - "min_width": 0, - }, - }, "log_session": True, "os": "windows", "record_session": True, @@ -66,17 +53,7 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: extension_id="extensionId", keep_alive=True, project_id="projectId", - proxies=[ - { - "type": "browserbase", - "domain_pattern": "domainPattern", - "geolocation": { - "country": "xx", - "city": "city", - "state": "xx", - }, - } - ], + proxies=True, region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -147,7 +124,7 @@ def test_method_update(self, client: Browserbase) -> None: id="id", status="REQUEST_RELEASE", ) - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize def test_method_update_with_all_params(self, client: Browserbase) -> None: @@ -156,7 +133,7 @@ def test_method_update_with_all_params(self, client: Browserbase) -> None: status="REQUEST_RELEASE", project_id="projectId", ) - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize def test_raw_response_update(self, client: Browserbase) -> None: @@ -168,7 +145,7 @@ def test_raw_response_update(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize def test_streaming_response_update(self, client: Browserbase) -> None: @@ -180,7 +157,7 @@ def test_streaming_response_update(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -230,7 +207,7 @@ def test_method_debug(self, client: Browserbase) -> None: session = client.sessions.debug( "id", ) - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize def test_raw_response_debug(self, client: Browserbase) -> None: @@ -241,7 +218,7 @@ def test_raw_response_debug(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize def test_streaming_response_debug(self, client: Browserbase) -> None: @@ -252,7 +229,7 @@ def test_streaming_response_debug(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -287,19 +264,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas "persist": True, }, "extension_id": "extensionId", - "fingerprint": { - "browsers": ["chrome"], - "devices": ["desktop"], - "http_version": "1", - "locales": ["string"], - "operating_systems": ["android"], - "screen": { - "max_height": 0, - "max_width": 0, - "min_height": 0, - "min_width": 0, - }, - }, "log_session": True, "os": "windows", "record_session": True, @@ -312,17 +276,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas extension_id="extensionId", keep_alive=True, project_id="projectId", - proxies=[ - { - "type": "browserbase", - "domain_pattern": "domainPattern", - "geolocation": { - "country": "xx", - "city": "city", - "state": "xx", - }, - } - ], + proxies=True, region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -393,7 +347,7 @@ async def test_method_update(self, async_client: AsyncBrowserbase) -> None: id="id", status="REQUEST_RELEASE", ) - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize async def test_method_update_with_all_params(self, async_client: AsyncBrowserbase) -> None: @@ -402,7 +356,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncBrowserbas status="REQUEST_RELEASE", project_id="projectId", ) - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None: @@ -414,7 +368,7 @@ async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize async def test_streaming_response_update(self, async_client: AsyncBrowserbase) -> None: @@ -426,7 +380,7 @@ async def test_streaming_response_update(self, async_client: AsyncBrowserbase) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -476,7 +430,7 @@ async def test_method_debug(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.debug( "id", ) - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -487,7 +441,7 @@ async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -498,7 +452,7 @@ async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) assert cast(Any, response.is_closed) is True From 94d9db6142e868b739649027144eade26e092cb3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:46:08 +0000 Subject: [PATCH 34/45] feat(api): manual updates --- .stats.yml | 4 +- api.md | 1 + src/browserbase/resources/contexts.py | 82 ++++++++++++++++++++++++++- tests/api_resources/test_contexts.py | 76 +++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 78eff16..9a94e54 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 18 +configured_endpoints: 19 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-b92143ddb16135de4ff65ce8bcdfe9991d11c73570f42f07ea27e0da86209a44.yml openapi_spec_hash: 16eb6e6c9687f01d2a791775b27dc315 -config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b +config_hash: b01d72cbe03bd762a73b05744086b2ec diff --git a/api.md b/api.md index dbb776f..d2d26e5 100644 --- a/api.md +++ b/api.md @@ -11,6 +11,7 @@ Methods: - client.contexts.create(\*\*params) -> ContextCreateResponse - client.contexts.retrieve(id) -> Context - client.contexts.update(id) -> ContextUpdateResponse +- client.contexts.delete(id) -> None # Extensions diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index 4379aa9..03af85f 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -5,7 +5,7 @@ import httpx from ..types import context_create_params -from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -145,6 +145,40 @@ def update( cast_to=ContextUpdateResponse, ) + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Context + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/v1/contexts/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class AsyncContextsResource(AsyncAPIResource): @cached_property @@ -268,6 +302,40 @@ async def update( cast_to=ContextUpdateResponse, ) + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Context + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/v1/contexts/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class ContextsResourceWithRawResponse: def __init__(self, contexts: ContextsResource) -> None: @@ -282,6 +350,9 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_raw_response_wrapper( contexts.update, ) + self.delete = to_raw_response_wrapper( + contexts.delete, + ) class AsyncContextsResourceWithRawResponse: @@ -297,6 +368,9 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_raw_response_wrapper( contexts.update, ) + self.delete = async_to_raw_response_wrapper( + contexts.delete, + ) class ContextsResourceWithStreamingResponse: @@ -312,6 +386,9 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_streamed_response_wrapper( contexts.update, ) + self.delete = to_streamed_response_wrapper( + contexts.delete, + ) class AsyncContextsResourceWithStreamingResponse: @@ -327,3 +404,6 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_streamed_response_wrapper( contexts.update, ) + self.delete = async_to_streamed_response_wrapper( + contexts.delete, + ) diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 83267ca..31fb97d 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -125,6 +125,44 @@ def test_path_params_update(self, client: Browserbase) -> None: "", ) + @parametrize + def test_method_delete(self, client: Browserbase) -> None: + context = client.contexts.delete( + "id", + ) + assert context is None + + @parametrize + def test_raw_response_delete(self, client: Browserbase) -> None: + response = client.contexts.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + context = response.parse() + assert context is None + + @parametrize + def test_streaming_response_delete(self, client: Browserbase) -> None: + with client.contexts.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + context = response.parse() + assert context is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Browserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.contexts.with_raw_response.delete( + "", + ) + class TestAsyncContexts: parametrize = pytest.mark.parametrize( @@ -238,3 +276,41 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: await async_client.contexts.with_raw_response.update( "", ) + + @parametrize + async def test_method_delete(self, async_client: AsyncBrowserbase) -> None: + context = await async_client.contexts.delete( + "id", + ) + assert context is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncBrowserbase) -> None: + response = await async_client.contexts.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + context = await response.parse() + assert context is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncBrowserbase) -> None: + async with async_client.contexts.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + context = await response.parse() + assert context is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncBrowserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.contexts.with_raw_response.delete( + "", + ) From 0b8da8acb8aa4a299655719a2b31d472f8d9750f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:51:03 +0000 Subject: [PATCH 35/45] feat(api): api update --- .stats.yml | 8 +- README.md | 1 + api.md | 29 ++-- src/browserbase/resources/contexts.py | 104 ++------------ src/browserbase/resources/extensions.py | 27 ++-- src/browserbase/resources/projects.py | 32 ++--- .../resources/sessions/sessions.py | 86 ++++++------ src/browserbase/types/__init__.py | 13 +- .../types/context_create_params.py | 4 +- ...ontext.py => context_retrieve_response.py} | 4 +- ...ension.py => extension_create_response.py} | 4 +- .../types/extension_retrieve_response.py | 22 +++ .../types/project_list_response.py | 27 +++- ...roject.py => project_retrieve_response.py} | 4 +- ...ect_usage.py => project_usage_response.py} | 4 +- .../types/session_create_params.py | 92 +++++++----- ...live_urls.py => session_debug_response.py} | 4 +- .../types/session_list_response.py | 58 +++++++- .../types/session_update_params.py | 12 +- ...{session.py => session_update_response.py} | 4 +- src/browserbase/types/sessions/__init__.py | 2 - .../types/sessions/log_list_response.py | 48 ++++++- .../sessions/recording_retrieve_response.py | 26 +++- src/browserbase/types/sessions/session_log.py | 46 ------ .../types/sessions/session_recording.py | 24 ---- tests/api_resources/test_contexts.py | 120 +++------------- tests/api_resources/test_extensions.py | 26 ++-- tests/api_resources/test_projects.py | 26 ++-- tests/api_resources/test_sessions.py | 132 ++++++++++++------ tests/test_client.py | 28 ++-- 30 files changed, 514 insertions(+), 503 deletions(-) rename src/browserbase/types/{context.py => context_retrieve_response.py} (84%) rename src/browserbase/types/{extension.py => extension_create_response.py} (85%) create mode 100644 src/browserbase/types/extension_retrieve_response.py rename src/browserbase/types/{project.py => project_retrieve_response.py} (87%) rename src/browserbase/types/{project_usage.py => project_usage_response.py} (78%) rename src/browserbase/types/{session_live_urls.py => session_debug_response.py} (88%) rename src/browserbase/types/{session.py => session_update_response.py} (95%) delete mode 100644 src/browserbase/types/sessions/session_log.py delete mode 100644 src/browserbase/types/sessions/session_recording.py diff --git a/.stats.yml b/.stats.yml index 9a94e54..b000c8c 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/browserbase%2Fbrowserbase-b92143ddb16135de4ff65ce8bcdfe9991d11c73570f42f07ea27e0da86209a44.yml -openapi_spec_hash: 16eb6e6c9687f01d2a791775b27dc315 -config_hash: b01d72cbe03bd762a73b05744086b2ec +configured_endpoints: 18 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-be7a4aeebb1605262935b4b3ab446a95b1fad8a7d18098943dd548c8a486ef13.yml +openapi_spec_hash: 1c950a109f80140711e7ae2cf87fddad +config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b diff --git a/README.md b/README.md index b379eb7..6863a76 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ from browserbase import Browserbase client = Browserbase() session = client.sessions.create( + project_id="projectId", browser_settings={}, ) print(session.browser_settings) diff --git a/api.md b/api.md index d2d26e5..0145485 100644 --- a/api.md +++ b/api.md @@ -3,28 +3,27 @@ Types: ```python -from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse +from browserbase.types import ContextCreateResponse, ContextRetrieveResponse, ContextUpdateResponse ``` Methods: - client.contexts.create(\*\*params) -> ContextCreateResponse -- client.contexts.retrieve(id) -> Context +- client.contexts.retrieve(id) -> ContextRetrieveResponse - client.contexts.update(id) -> ContextUpdateResponse -- client.contexts.delete(id) -> None # Extensions Types: ```python -from browserbase.types import Extension +from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse ``` Methods: -- client.extensions.create(\*\*params) -> Extension -- client.extensions.retrieve(id) -> Extension +- client.extensions.create(\*\*params) -> ExtensionCreateResponse +- client.extensions.retrieve(id) -> ExtensionRetrieveResponse - client.extensions.delete(id) -> None # Projects @@ -32,14 +31,14 @@ Methods: Types: ```python -from browserbase.types import Project, ProjectUsage, ProjectListResponse +from browserbase.types import ProjectRetrieveResponse, ProjectListResponse, ProjectUsageResponse ``` Methods: -- client.projects.retrieve(id) -> Project +- client.projects.retrieve(id) -> ProjectRetrieveResponse - client.projects.list() -> ProjectListResponse -- client.projects.usage(id) -> ProjectUsage +- client.projects.usage(id) -> ProjectUsageResponse # Sessions @@ -47,11 +46,11 @@ Types: ```python from browserbase.types import ( - Session, - SessionLiveURLs, SessionCreateResponse, SessionRetrieveResponse, + SessionUpdateResponse, SessionListResponse, + SessionDebugResponse, ) ``` @@ -59,9 +58,9 @@ Methods: - client.sessions.create(\*\*params) -> SessionCreateResponse - client.sessions.retrieve(id) -> SessionRetrieveResponse -- client.sessions.update(id, \*\*params) -> Session +- client.sessions.update(id, \*\*params) -> SessionUpdateResponse - client.sessions.list(\*\*params) -> SessionListResponse -- client.sessions.debug(id) -> SessionLiveURLs +- client.sessions.debug(id) -> SessionDebugResponse ## Downloads @@ -74,7 +73,7 @@ Methods: Types: ```python -from browserbase.types.sessions import SessionLog, LogListResponse +from browserbase.types.sessions import LogListResponse ``` Methods: @@ -86,7 +85,7 @@ Methods: Types: ```python -from browserbase.types.sessions import SessionRecording, RecordingRetrieveResponse +from browserbase.types.sessions import RecordingRetrieveResponse ``` Methods: diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index 03af85f..d2bb416 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -5,7 +5,7 @@ import httpx from ..types import context_create_params -from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._types import Body, Query, Headers, NotGiven, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -16,9 +16,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.context import Context from ..types.context_create_response import ContextCreateResponse from ..types.context_update_response import ContextUpdateResponse +from ..types.context_retrieve_response import ContextRetrieveResponse __all__ = ["ContextsResource", "AsyncContextsResource"] @@ -46,7 +46,7 @@ def with_streaming_response(self) -> ContextsResourceWithStreamingResponse: def create( self, *, - project_id: str | Omit = omit, + project_id: str, # 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, @@ -89,9 +89,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Context: + ) -> ContextRetrieveResponse: """ - Context + Get a Context Args: extra_headers: Send extra headers @@ -109,7 +109,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Context, + cast_to=ContextRetrieveResponse, ) def update( @@ -124,7 +124,7 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update Context + Update a Context Args: extra_headers: Send extra headers @@ -145,40 +145,6 @@ def update( cast_to=ContextUpdateResponse, ) - def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Delete Context - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( - f"/v1/contexts/{id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - class AsyncContextsResource(AsyncAPIResource): @cached_property @@ -203,7 +169,7 @@ def with_streaming_response(self) -> AsyncContextsResourceWithStreamingResponse: async def create( self, *, - project_id: str | Omit = omit, + project_id: str, # 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, @@ -246,9 +212,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Context: + ) -> ContextRetrieveResponse: """ - Context + Get a Context Args: extra_headers: Send extra headers @@ -266,7 +232,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Context, + cast_to=ContextRetrieveResponse, ) async def update( @@ -281,7 +247,7 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update Context + Update a Context Args: extra_headers: Send extra headers @@ -302,40 +268,6 @@ async def update( cast_to=ContextUpdateResponse, ) - async def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Delete Context - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._delete( - f"/v1/contexts/{id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - class ContextsResourceWithRawResponse: def __init__(self, contexts: ContextsResource) -> None: @@ -350,9 +282,6 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_raw_response_wrapper( contexts.update, ) - self.delete = to_raw_response_wrapper( - contexts.delete, - ) class AsyncContextsResourceWithRawResponse: @@ -368,9 +297,6 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_raw_response_wrapper( contexts.update, ) - self.delete = async_to_raw_response_wrapper( - contexts.delete, - ) class ContextsResourceWithStreamingResponse: @@ -386,9 +312,6 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_streamed_response_wrapper( contexts.update, ) - self.delete = to_streamed_response_wrapper( - contexts.delete, - ) class AsyncContextsResourceWithStreamingResponse: @@ -404,6 +327,3 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_streamed_response_wrapper( contexts.update, ) - self.delete = async_to_streamed_response_wrapper( - contexts.delete, - ) diff --git a/src/browserbase/resources/extensions.py b/src/browserbase/resources/extensions.py index 882a495..21d06e7 100644 --- a/src/browserbase/resources/extensions.py +++ b/src/browserbase/resources/extensions.py @@ -18,7 +18,8 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.extension import Extension +from ..types.extension_create_response import ExtensionCreateResponse +from ..types.extension_retrieve_response import ExtensionRetrieveResponse __all__ = ["ExtensionsResource", "AsyncExtensionsResource"] @@ -53,7 +54,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionCreateResponse: """ Upload an Extension @@ -79,7 +80,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionCreateResponse, ) def retrieve( @@ -92,9 +93,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionRetrieveResponse: """ - Extension + Get an Extension Args: extra_headers: Send extra headers @@ -112,7 +113,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionRetrieveResponse, ) def delete( @@ -127,7 +128,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete Extension + Delete an Extension Args: extra_headers: Send extra headers @@ -180,7 +181,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionCreateResponse: """ Upload an Extension @@ -206,7 +207,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionCreateResponse, ) async def retrieve( @@ -219,9 +220,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionRetrieveResponse: """ - Extension + Get an Extension Args: extra_headers: Send extra headers @@ -239,7 +240,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionRetrieveResponse, ) async def delete( @@ -254,7 +255,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete Extension + Delete an Extension Args: extra_headers: Send extra headers diff --git a/src/browserbase/resources/projects.py b/src/browserbase/resources/projects.py index a6ae633..62c28af 100644 --- a/src/browserbase/resources/projects.py +++ b/src/browserbase/resources/projects.py @@ -14,9 +14,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.project import Project -from ..types.project_usage import ProjectUsage from ..types.project_list_response import ProjectListResponse +from ..types.project_usage_response import ProjectUsageResponse +from ..types.project_retrieve_response import ProjectRetrieveResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -51,9 +51,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Project: + ) -> ProjectRetrieveResponse: """ - Project + Get a Project Args: extra_headers: Send extra headers @@ -71,7 +71,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Project, + cast_to=ProjectRetrieveResponse, ) def list( @@ -84,7 +84,7 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List projects""" + """List Projects""" return self._get( "/v1/projects", options=make_request_options( @@ -103,9 +103,9 @@ def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsage: + ) -> ProjectUsageResponse: """ - Project Usage + Get Project Usage Args: extra_headers: Send extra headers @@ -123,7 +123,7 @@ def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsage, + cast_to=ProjectUsageResponse, ) @@ -157,9 +157,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Project: + ) -> ProjectRetrieveResponse: """ - Project + Get a Project Args: extra_headers: Send extra headers @@ -177,7 +177,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Project, + cast_to=ProjectRetrieveResponse, ) async def list( @@ -190,7 +190,7 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List projects""" + """List Projects""" return await self._get( "/v1/projects", options=make_request_options( @@ -209,9 +209,9 @@ async def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsage: + ) -> ProjectUsageResponse: """ - Project Usage + Get Project Usage Args: extra_headers: Send extra headers @@ -229,7 +229,7 @@ async def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsage, + cast_to=ProjectUsageResponse, ) diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 09fd15a..ceaaeb8 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -51,10 +51,10 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.session import Session -from ...types.session_live_urls import SessionLiveURLs from ...types.session_list_response import SessionListResponse +from ...types.session_debug_response import SessionDebugResponse from ...types.session_create_response import SessionCreateResponse +from ...types.session_update_response import SessionUpdateResponse from ...types.session_retrieve_response import SessionRetrieveResponse __all__ = ["SessionsResource", "AsyncSessionsResource"] @@ -99,11 +99,11 @@ def with_streaming_response(self) -> SessionsResourceWithStreamingResponse: def create( self, *, + project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, - project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, + proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -117,17 +117,17 @@ def create( """Create a Session Args: - extension_id: The uploaded Extension ID. + project_id: The Project ID. - See + Can be found in + [Settings](https://www.browserbase.com/settings). + + extension_id: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. - project_id: The Project ID. Can be found in - [Settings](https://www.browserbase.com/settings). - proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -151,10 +151,10 @@ def create( "/v1/sessions", body=maybe_transform( { + "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, - "project_id": project_id, "proxies": proxies, "region": region, "api_timeout": api_timeout, @@ -180,7 +180,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Session + Get a Session Args: extra_headers: Send extra headers @@ -205,25 +205,26 @@ def update( self, id: str, *, + project_id: str, status: Literal["REQUEST_RELEASE"], - project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Session: - """ - Update Session + ) -> SessionUpdateResponse: + """Update a Session Args: - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before - session's timeout to avoid additional charges. + project_id: The Project ID. - project_id: The Project ID. Can be found in + Can be found in [Settings](https://www.browserbase.com/settings). + status: Set to `REQUEST_RELEASE` to request that the session complete. Use before + session's timeout to avoid additional charges. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -238,15 +239,15 @@ def update( f"/v1/sessions/{id}", body=maybe_transform( { - "status": status, "project_id": project_id, + "status": status, }, session_update_params.SessionUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Session, + cast_to=SessionUpdateResponse, ) def list( @@ -306,7 +307,7 @@ def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionLiveURLs: + ) -> SessionDebugResponse: """ Session Live URLs @@ -326,7 +327,7 @@ def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionLiveURLs, + cast_to=SessionDebugResponse, ) @@ -369,11 +370,11 @@ def with_streaming_response(self) -> AsyncSessionsResourceWithStreamingResponse: async def create( self, *, + project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, - project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, + proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -387,17 +388,17 @@ async def create( """Create a Session Args: - extension_id: The uploaded Extension ID. + project_id: The Project ID. - See + Can be found in + [Settings](https://www.browserbase.com/settings). + + extension_id: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. - project_id: The Project ID. Can be found in - [Settings](https://www.browserbase.com/settings). - proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -421,10 +422,10 @@ async def create( "/v1/sessions", body=await async_maybe_transform( { + "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, - "project_id": project_id, "proxies": proxies, "region": region, "api_timeout": api_timeout, @@ -450,7 +451,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Session + Get a Session Args: extra_headers: Send extra headers @@ -475,25 +476,26 @@ async def update( self, id: str, *, + project_id: str, status: Literal["REQUEST_RELEASE"], - project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Session: - """ - Update Session + ) -> SessionUpdateResponse: + """Update a Session Args: - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before - session's timeout to avoid additional charges. + project_id: The Project ID. - project_id: The Project ID. Can be found in + Can be found in [Settings](https://www.browserbase.com/settings). + status: Set to `REQUEST_RELEASE` to request that the session complete. Use before + session's timeout to avoid additional charges. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -508,15 +510,15 @@ async def update( f"/v1/sessions/{id}", body=await async_maybe_transform( { - "status": status, "project_id": project_id, + "status": status, }, session_update_params.SessionUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Session, + cast_to=SessionUpdateResponse, ) async def list( @@ -576,7 +578,7 @@ async def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionLiveURLs: + ) -> SessionDebugResponse: """ Session Live URLs @@ -596,7 +598,7 @@ async def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionLiveURLs, + cast_to=SessionDebugResponse, ) diff --git a/src/browserbase/types/__init__.py b/src/browserbase/types/__init__.py index 4dd85dd..20e2f90 100644 --- a/src/browserbase/types/__init__.py +++ b/src/browserbase/types/__init__.py @@ -2,20 +2,21 @@ from __future__ import annotations -from .context import Context as Context -from .project import Project as Project -from .session import Session as Session -from .extension import Extension as Extension -from .project_usage import ProjectUsage as ProjectUsage -from .session_live_urls import SessionLiveURLs as SessionLiveURLs from .session_list_params import SessionListParams as SessionListParams from .context_create_params import ContextCreateParams as ContextCreateParams from .project_list_response import ProjectListResponse as ProjectListResponse from .session_create_params import SessionCreateParams as SessionCreateParams from .session_list_response import SessionListResponse as SessionListResponse from .session_update_params import SessionUpdateParams as SessionUpdateParams +from .project_usage_response import ProjectUsageResponse as ProjectUsageResponse +from .session_debug_response import SessionDebugResponse as SessionDebugResponse from .context_create_response import ContextCreateResponse as ContextCreateResponse from .context_update_response import ContextUpdateResponse as ContextUpdateResponse from .extension_create_params import ExtensionCreateParams as ExtensionCreateParams from .session_create_response import SessionCreateResponse as SessionCreateResponse +from .session_update_response import SessionUpdateResponse as SessionUpdateResponse +from .context_retrieve_response import ContextRetrieveResponse as ContextRetrieveResponse +from .extension_create_response import ExtensionCreateResponse as ExtensionCreateResponse +from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .session_retrieve_response import SessionRetrieveResponse as SessionRetrieveResponse +from .extension_retrieve_response import ExtensionRetrieveResponse as ExtensionRetrieveResponse diff --git a/src/browserbase/types/context_create_params.py b/src/browserbase/types/context_create_params.py index 66c6c46..75cd1fc 100644 --- a/src/browserbase/types/context_create_params.py +++ b/src/browserbase/types/context_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Annotated, TypedDict +from typing_extensions import Required, Annotated, TypedDict from .._utils import PropertyInfo @@ -10,7 +10,7 @@ class ContextCreateParams(TypedDict, total=False): - project_id: Annotated[str, PropertyInfo(alias="projectId")] + project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] """The Project ID. Can be found in [Settings](https://www.browserbase.com/settings). diff --git a/src/browserbase/types/context.py b/src/browserbase/types/context_retrieve_response.py similarity index 84% rename from src/browserbase/types/context.py rename to src/browserbase/types/context_retrieve_response.py index cb5c32f..c2cd692 100644 --- a/src/browserbase/types/context.py +++ b/src/browserbase/types/context_retrieve_response.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["Context"] +__all__ = ["ContextRetrieveResponse"] -class Context(BaseModel): +class ContextRetrieveResponse(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension.py b/src/browserbase/types/extension_create_response.py similarity index 85% rename from src/browserbase/types/extension.py rename to src/browserbase/types/extension_create_response.py index 94582c3..d2b74f4 100644 --- a/src/browserbase/types/extension.py +++ b/src/browserbase/types/extension_create_response.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["Extension"] +__all__ = ["ExtensionCreateResponse"] -class Extension(BaseModel): +class ExtensionCreateResponse(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension_retrieve_response.py b/src/browserbase/types/extension_retrieve_response.py new file mode 100644 index 0000000..c786348 --- /dev/null +++ b/src/browserbase/types/extension_retrieve_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["ExtensionRetrieveResponse"] + + +class ExtensionRetrieveResponse(BaseModel): + id: str + + created_at: datetime = FieldInfo(alias="createdAt") + + file_name: str = FieldInfo(alias="fileName") + + project_id: str = FieldInfo(alias="projectId") + """The Project ID linked to the uploaded Extension.""" + + updated_at: datetime = FieldInfo(alias="updatedAt") diff --git a/src/browserbase/types/project_list_response.py b/src/browserbase/types/project_list_response.py index 2d05a23..e364b52 100644 --- a/src/browserbase/types/project_list_response.py +++ b/src/browserbase/types/project_list_response.py @@ -1,10 +1,31 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List +from datetime import datetime from typing_extensions import TypeAlias -from .project import Project +from pydantic import Field as FieldInfo -__all__ = ["ProjectListResponse"] +from .._models import BaseModel -ProjectListResponse: TypeAlias = List[Project] +__all__ = ["ProjectListResponse", "ProjectListResponseItem"] + + +class ProjectListResponseItem(BaseModel): + id: str + + concurrency: int + """The maximum number of sessions that this project can run concurrently.""" + + created_at: datetime = FieldInfo(alias="createdAt") + + default_timeout: int = FieldInfo(alias="defaultTimeout") + + name: str + + owner_id: str = FieldInfo(alias="ownerId") + + updated_at: datetime = FieldInfo(alias="updatedAt") + + +ProjectListResponse: TypeAlias = List[ProjectListResponseItem] diff --git a/src/browserbase/types/project.py b/src/browserbase/types/project_retrieve_response.py similarity index 87% rename from src/browserbase/types/project.py rename to src/browserbase/types/project_retrieve_response.py index dc3cf33..7812667 100644 --- a/src/browserbase/types/project.py +++ b/src/browserbase/types/project_retrieve_response.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["Project"] +__all__ = ["ProjectRetrieveResponse"] -class Project(BaseModel): +class ProjectRetrieveResponse(BaseModel): id: str concurrency: int diff --git a/src/browserbase/types/project_usage.py b/src/browserbase/types/project_usage_response.py similarity index 78% rename from src/browserbase/types/project_usage.py rename to src/browserbase/types/project_usage_response.py index c8a03f5..b52fccf 100644 --- a/src/browserbase/types/project_usage.py +++ b/src/browserbase/types/project_usage_response.py @@ -4,10 +4,10 @@ from .._models import BaseModel -__all__ = ["ProjectUsage"] +__all__ = ["ProjectUsageResponse"] -class ProjectUsage(BaseModel): +class ProjectUsageResponse(BaseModel): browser_minutes: int = FieldInfo(alias="browserMinutes") proxy_bytes: int = FieldInfo(alias="proxyBytes") diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 33dd6fd..2ba3640 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -2,25 +2,33 @@ from __future__ import annotations -from typing import Dict, Union, Iterable +from typing import Dict, List, Union, Iterable from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo __all__ = [ "SessionCreateParams", "BrowserSettings", "BrowserSettingsContext", + "BrowserSettingsFingerprint", + "BrowserSettingsFingerprintScreen", "BrowserSettingsViewport", - "ProxiesUnionMember1", - "ProxiesUnionMember1BrowserbaseProxyConfig", - "ProxiesUnionMember1BrowserbaseProxyConfigGeolocation", - "ProxiesUnionMember1ExternalProxyConfig", - "ProxiesUnionMember1NoneProxyConfig", + "ProxiesUnionMember0", + "ProxiesUnionMember0UnionMember0", + "ProxiesUnionMember0UnionMember0Geolocation", + "ProxiesUnionMember0UnionMember1", ] class SessionCreateParams(TypedDict, total=False): + project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] + """The Project ID. + + Can be found in [Settings](https://www.browserbase.com/settings). + """ + browser_settings: Annotated[BrowserSettings, PropertyInfo(alias="browserSettings")] extension_id: Annotated[str, PropertyInfo(alias="extensionId")] @@ -35,13 +43,7 @@ class SessionCreateParams(TypedDict, total=False): Available on the Hobby Plan and above. """ - project_id: Annotated[str, PropertyInfo(alias="projectId")] - """The Project ID. - - Can be found in [Settings](https://www.browserbase.com/settings). - """ - - proxies: Union[bool, Iterable[ProxiesUnionMember1]] + proxies: Union[Iterable[ProxiesUnionMember0], bool] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -72,10 +74,42 @@ class BrowserSettingsContext(TypedDict, total=False): """Whether or not to persist the context after browsing. Defaults to `false`.""" +class BrowserSettingsFingerprintScreen(TypedDict, total=False): + max_height: Annotated[int, PropertyInfo(alias="maxHeight")] + + max_width: Annotated[int, PropertyInfo(alias="maxWidth")] + + min_height: Annotated[int, PropertyInfo(alias="minHeight")] + + min_width: Annotated[int, PropertyInfo(alias="minWidth")] + + +class BrowserSettingsFingerprint(TypedDict, total=False): + """ + See usage examples [on the Stealth Mode page](/features/stealth-mode#fingerprinting) + """ + + browsers: List[Literal["chrome", "edge", "firefox", "safari"]] + + devices: List[Literal["desktop", "mobile"]] + + http_version: Annotated[Literal["1", "2"], PropertyInfo(alias="httpVersion")] + + locales: SequenceNotStr[str] + + operating_systems: Annotated[ + List[Literal["android", "ios", "linux", "macos", "windows"]], PropertyInfo(alias="operatingSystems") + ] + + screen: BrowserSettingsFingerprintScreen + + class BrowserSettingsViewport(TypedDict, total=False): height: int + """The height of the browser.""" width: int + """The width of the browser.""" class BrowserSettings(TypedDict, total=False): @@ -105,6 +139,12 @@ class BrowserSettings(TypedDict, total=False): See [Upload Extension](/reference/api/upload-an-extension). """ + fingerprint: BrowserSettingsFingerprint + """ + See usage examples + [on the Stealth Mode page](/features/stealth-mode#fingerprinting) + """ + log_session: Annotated[bool, PropertyInfo(alias="logSession")] """Enable or disable session logging. Defaults to `true`.""" @@ -123,8 +163,8 @@ class BrowserSettings(TypedDict, total=False): viewport: BrowserSettingsViewport -class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): - """Configuration for geolocation""" +class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): + """Geographic location for the proxy. Optional.""" country: Required[str] """Country code in ISO 3166-1 alpha-2 format""" @@ -136,7 +176,7 @@ class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=Fals """US state code (2 characters). Must also specify US as the country. Optional.""" -class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): +class ProxiesUnionMember0UnionMember0(TypedDict, total=False): type: Required[Literal["browserbase"]] """Type of proxy. @@ -149,11 +189,11 @@ class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): If omitted, defaults to all domains. Optional. """ - geolocation: ProxiesUnionMember1BrowserbaseProxyConfigGeolocation - """Configuration for geolocation""" + geolocation: ProxiesUnionMember0UnionMember0Geolocation + """Geographic location for the proxy. Optional.""" -class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): +class ProxiesUnionMember0UnionMember1(TypedDict, total=False): server: Required[str] """Server URL for external proxy. Required.""" @@ -173,16 +213,4 @@ class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): """Username for external proxy authentication. Optional.""" -class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): - domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] - """Domain pattern for which site should have proxies disabled.""" - - type: Required[Literal["none"]] - """Type of proxy. Use 'none' to disable proxy for matching domains.""" - - -ProxiesUnionMember1: TypeAlias = Union[ - ProxiesUnionMember1BrowserbaseProxyConfig, - ProxiesUnionMember1ExternalProxyConfig, - ProxiesUnionMember1NoneProxyConfig, -] +ProxiesUnionMember0: TypeAlias = Union[ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1] diff --git a/src/browserbase/types/session_live_urls.py b/src/browserbase/types/session_debug_response.py similarity index 88% rename from src/browserbase/types/session_live_urls.py rename to src/browserbase/types/session_debug_response.py index 3c7ba32..9cee7a7 100644 --- a/src/browserbase/types/session_live_urls.py +++ b/src/browserbase/types/session_debug_response.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["SessionLiveURLs", "Page"] +__all__ = ["SessionDebugResponse", "Page"] class Page(BaseModel): @@ -23,7 +23,7 @@ class Page(BaseModel): url: str -class SessionLiveURLs(BaseModel): +class SessionDebugResponse(BaseModel): debugger_fullscreen_url: str = FieldInfo(alias="debuggerFullscreenUrl") debugger_url: str = FieldInfo(alias="debuggerUrl") diff --git a/src/browserbase/types/session_list_response.py b/src/browserbase/types/session_list_response.py index ca162dd..4c1bd88 100644 --- a/src/browserbase/types/session_list_response.py +++ b/src/browserbase/types/session_list_response.py @@ -1,10 +1,58 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List -from typing_extensions import TypeAlias +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias -from .session import Session +from pydantic import Field as FieldInfo -__all__ = ["SessionListResponse"] +from .._models import BaseModel -SessionListResponse: TypeAlias = List[Session] +__all__ = ["SessionListResponse", "SessionListResponseItem"] + + +class SessionListResponseItem(BaseModel): + id: str + + created_at: datetime = FieldInfo(alias="createdAt") + + expires_at: datetime = FieldInfo(alias="expiresAt") + + keep_alive: bool = FieldInfo(alias="keepAlive") + """Indicates if the Session was created to be kept alive upon disconnections""" + + project_id: str = FieldInfo(alias="projectId") + """The Project ID linked to the Session.""" + + proxy_bytes: int = FieldInfo(alias="proxyBytes") + """Bytes used via the [Proxy](/features/stealth-mode#proxies-and-residential-ips)""" + + region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] + """The region where the Session is running.""" + + started_at: datetime = FieldInfo(alias="startedAt") + + status: Literal["RUNNING", "ERROR", "TIMED_OUT", "COMPLETED"] + + updated_at: datetime = FieldInfo(alias="updatedAt") + + avg_cpu_usage: Optional[int] = FieldInfo(alias="avgCpuUsage", default=None) + """CPU used by the Session""" + + context_id: Optional[str] = FieldInfo(alias="contextId", default=None) + """Optional. The Context linked to the Session.""" + + ended_at: Optional[datetime] = FieldInfo(alias="endedAt", default=None) + + memory_usage: Optional[int] = FieldInfo(alias="memoryUsage", default=None) + """Memory used by the Session""" + + user_metadata: Optional[Dict[str, object]] = FieldInfo(alias="userMetadata", default=None) + """Arbitrary user metadata to attach to the session. + + To learn more about user metadata, see + [User Metadata](/features/sessions#user-metadata). + """ + + +SessionListResponse: TypeAlias = List[SessionListResponseItem] diff --git a/src/browserbase/types/session_update_params.py b/src/browserbase/types/session_update_params.py index 71c589d..66dcd35 100644 --- a/src/browserbase/types/session_update_params.py +++ b/src/browserbase/types/session_update_params.py @@ -10,14 +10,14 @@ class SessionUpdateParams(TypedDict, total=False): - status: Required[Literal["REQUEST_RELEASE"]] - """Set to `REQUEST_RELEASE` to request that the session complete. + project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] + """The Project ID. - Use before session's timeout to avoid additional charges. + Can be found in [Settings](https://www.browserbase.com/settings). """ - project_id: Annotated[str, PropertyInfo(alias="projectId")] - """The Project ID. + status: Required[Literal["REQUEST_RELEASE"]] + """Set to `REQUEST_RELEASE` to request that the session complete. - Can be found in [Settings](https://www.browserbase.com/settings). + Use before session's timeout to avoid additional charges. """ diff --git a/src/browserbase/types/session.py b/src/browserbase/types/session_update_response.py similarity index 95% rename from src/browserbase/types/session.py rename to src/browserbase/types/session_update_response.py index 16450e2..67a1371 100644 --- a/src/browserbase/types/session.py +++ b/src/browserbase/types/session_update_response.py @@ -8,10 +8,10 @@ from .._models import BaseModel -__all__ = ["Session"] +__all__ = ["SessionUpdateResponse"] -class Session(BaseModel): +class SessionUpdateResponse(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/sessions/__init__.py b/src/browserbase/types/sessions/__init__.py index 0cef6b1..69d5470 100644 --- a/src/browserbase/types/sessions/__init__.py +++ b/src/browserbase/types/sessions/__init__.py @@ -2,9 +2,7 @@ from __future__ import annotations -from .session_log import SessionLog as SessionLog from .log_list_response import LogListResponse as LogListResponse -from .session_recording import SessionRecording as SessionRecording from .upload_create_params import UploadCreateParams as UploadCreateParams from .upload_create_response import UploadCreateResponse as UploadCreateResponse from .recording_retrieve_response import RecordingRetrieveResponse as RecordingRetrieveResponse diff --git a/src/browserbase/types/sessions/log_list_response.py b/src/browserbase/types/sessions/log_list_response.py index 2b325a8..efd848a 100644 --- a/src/browserbase/types/sessions/log_list_response.py +++ b/src/browserbase/types/sessions/log_list_response.py @@ -1,10 +1,50 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import Dict, List, Optional from typing_extensions import TypeAlias -from .session_log import SessionLog +from pydantic import Field as FieldInfo -__all__ = ["LogListResponse"] +from ..._models import BaseModel -LogListResponse: TypeAlias = List[SessionLog] +__all__ = ["LogListResponse", "LogListResponseItem", "LogListResponseItemRequest", "LogListResponseItemResponse"] + + +class LogListResponseItemRequest(BaseModel): + params: Dict[str, object] + + raw_body: str = FieldInfo(alias="rawBody") + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class LogListResponseItemResponse(BaseModel): + raw_body: str = FieldInfo(alias="rawBody") + + result: Dict[str, object] + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class LogListResponseItem(BaseModel): + method: str + + page_id: int = FieldInfo(alias="pageId") + + session_id: str = FieldInfo(alias="sessionId") + + frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) + + loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) + + request: Optional[LogListResponseItemRequest] = None + + response: Optional[LogListResponseItemResponse] = None + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +LogListResponse: TypeAlias = List[LogListResponseItem] diff --git a/src/browserbase/types/sessions/recording_retrieve_response.py b/src/browserbase/types/sessions/recording_retrieve_response.py index 951969b..d3613b8 100644 --- a/src/browserbase/types/sessions/recording_retrieve_response.py +++ b/src/browserbase/types/sessions/recording_retrieve_response.py @@ -1,10 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import Dict, List from typing_extensions import TypeAlias -from .session_recording import SessionRecording +from pydantic import Field as FieldInfo -__all__ = ["RecordingRetrieveResponse"] +from ..._models import BaseModel -RecordingRetrieveResponse: TypeAlias = List[SessionRecording] +__all__ = ["RecordingRetrieveResponse", "RecordingRetrieveResponseItem"] + + +class RecordingRetrieveResponseItem(BaseModel): + data: Dict[str, object] + """ + See + [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). + """ + + session_id: str = FieldInfo(alias="sessionId") + + timestamp: int + """milliseconds that have elapsed since the UNIX epoch""" + + type: int + + +RecordingRetrieveResponse: TypeAlias = List[RecordingRetrieveResponseItem] diff --git a/src/browserbase/types/sessions/session_log.py b/src/browserbase/types/sessions/session_log.py deleted file mode 100644 index 428f518..0000000 --- a/src/browserbase/types/sessions/session_log.py +++ /dev/null @@ -1,46 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, Optional - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["SessionLog", "Request", "Response"] - - -class Request(BaseModel): - params: Dict[str, object] - - raw_body: str = FieldInfo(alias="rawBody") - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class Response(BaseModel): - raw_body: str = FieldInfo(alias="rawBody") - - result: Dict[str, object] - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class SessionLog(BaseModel): - method: str - - page_id: int = FieldInfo(alias="pageId") - - session_id: str = FieldInfo(alias="sessionId") - - frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) - - loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) - - request: Optional[Request] = None - - response: Optional[Response] = None - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" diff --git a/src/browserbase/types/sessions/session_recording.py b/src/browserbase/types/sessions/session_recording.py deleted file mode 100644 index c847137..0000000 --- a/src/browserbase/types/sessions/session_recording.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["SessionRecording"] - - -class SessionRecording(BaseModel): - data: Dict[str, object] - """ - See - [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). - """ - - session_id: str = FieldInfo(alias="sessionId") - - timestamp: int - """milliseconds that have elapsed since the UNIX epoch""" - - type: int diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 31fb97d..4ad2773 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -9,7 +9,11 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse +from browserbase.types import ( + ContextCreateResponse, + ContextUpdateResponse, + ContextRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -19,11 +23,6 @@ class TestContexts: @parametrize def test_method_create(self, client: Browserbase) -> None: - context = client.contexts.create() - assert_matches_type(ContextCreateResponse, context, path=["response"]) - - @parametrize - def test_method_create_with_all_params(self, client: Browserbase) -> None: context = client.contexts.create( project_id="projectId", ) @@ -31,7 +30,9 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.create() + response = client.contexts.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -40,7 +41,9 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.create() as response: + with client.contexts.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -54,7 +57,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: context = client.contexts.retrieve( "id", ) - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -65,7 +68,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -76,7 +79,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -125,44 +128,6 @@ def test_path_params_update(self, client: Browserbase) -> None: "", ) - @parametrize - def test_method_delete(self, client: Browserbase) -> None: - context = client.contexts.delete( - "id", - ) - assert context is None - - @parametrize - def test_raw_response_delete(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - context = response.parse() - assert context is None - - @parametrize - def test_streaming_response_delete(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - context = response.parse() - assert context is None - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_delete(self, client: Browserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.contexts.with_raw_response.delete( - "", - ) - class TestAsyncContexts: parametrize = pytest.mark.parametrize( @@ -171,11 +136,6 @@ class TestAsyncContexts: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: - context = await async_client.contexts.create() - assert_matches_type(ContextCreateResponse, context, path=["response"]) - - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.create( project_id="projectId", ) @@ -183,7 +143,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.create() + response = await async_client.contexts.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -192,7 +154,9 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.create() as response: + async with async_client.contexts.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -206,7 +170,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.retrieve( "id", ) - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -217,7 +181,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -228,7 +192,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -276,41 +240,3 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: await async_client.contexts.with_raw_response.update( "", ) - - @parametrize - async def test_method_delete(self, async_client: AsyncBrowserbase) -> None: - context = await async_client.contexts.delete( - "id", - ) - assert context is None - - @parametrize - async def test_raw_response_delete(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - context = await response.parse() - assert context is None - - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - context = await response.parse() - assert context is None - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_delete(self, async_client: AsyncBrowserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.contexts.with_raw_response.delete( - "", - ) diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py index 6b6a018..e32ae9b 100644 --- a/tests/api_resources/test_extensions.py +++ b/tests/api_resources/test_extensions.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import Extension +from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_create(self, client: Browserbase) -> None: extension = client.extensions.create( file=b"raw file contents", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize def test_raw_response_create(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_create(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: extension = client.extensions.retrieve( "id", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -64,7 +64,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -75,7 +75,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -135,7 +135,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.create( file=b"raw file contents", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -146,7 +146,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -157,7 +157,7 @@ async def test_streaming_response_create(self, async_client: AsyncBrowserbase) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -166,7 +166,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.retrieve( "id", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -177,7 +177,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -188,7 +188,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index c8241bf..0d8e3c9 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import Project, ProjectUsage, ProjectListResponse +from browserbase.types import ProjectListResponse, ProjectUsageResponse, ProjectRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: project = client.projects.retrieve( "id", ) - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -85,7 +85,7 @@ def test_method_usage(self, client: Browserbase) -> None: project = client.projects.usage( "id", ) - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize def test_raw_response_usage(self, client: Browserbase) -> None: @@ -96,7 +96,7 @@ def test_raw_response_usage(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize def test_streaming_response_usage(self, client: Browserbase) -> None: @@ -107,7 +107,7 @@ def test_streaming_response_usage(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -129,7 +129,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.retrieve( "id", ) - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -140,7 +140,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -151,7 +151,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -192,7 +192,7 @@ async def test_method_usage(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.usage( "id", ) - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -203,7 +203,7 @@ async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -214,7 +214,7 @@ async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 6a21aa8..7a16f64 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -10,10 +10,10 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type from browserbase.types import ( - Session, - SessionLiveURLs, SessionListResponse, + SessionDebugResponse, SessionCreateResponse, + SessionUpdateResponse, SessionRetrieveResponse, ) @@ -25,12 +25,15 @@ class TestSessions: @parametrize def test_method_create(self, client: Browserbase) -> None: - session = client.sessions.create() + session = client.sessions.create( + project_id="projectId", + ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Browserbase) -> None: session = client.sessions.create( + project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -41,6 +44,19 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: "persist": True, }, "extension_id": "extensionId", + "fingerprint": { + "browsers": ["chrome"], + "devices": ["desktop"], + "http_version": "1", + "locales": ["string"], + "operating_systems": ["android"], + "screen": { + "max_height": 0, + "max_width": 0, + "min_height": 0, + "min_width": 0, + }, + }, "log_session": True, "os": "windows", "record_session": True, @@ -52,8 +68,17 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: }, extension_id="extensionId", keep_alive=True, - project_id="projectId", - proxies=True, + proxies=[ + { + "type": "browserbase", + "domain_pattern": "domainPattern", + "geolocation": { + "country": "xx", + "city": "city", + "state": "xx", + }, + } + ], region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -62,7 +87,9 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.sessions.with_raw_response.create() + response = client.sessions.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -71,7 +98,9 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.sessions.with_streaming_response.create() as response: + with client.sessions.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -122,42 +151,36 @@ def test_path_params_retrieve(self, client: Browserbase) -> None: def test_method_update(self, client: Browserbase) -> None: session = client.sessions.update( id="id", - status="REQUEST_RELEASE", - ) - assert_matches_type(Session, session, path=["response"]) - - @parametrize - def test_method_update_with_all_params(self, client: Browserbase) -> None: - session = client.sessions.update( - id="id", - status="REQUEST_RELEASE", project_id="projectId", + status="REQUEST_RELEASE", ) - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize def test_raw_response_update(self, client: Browserbase) -> None: response = client.sessions.with_raw_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize def test_streaming_response_update(self, client: Browserbase) -> None: with client.sessions.with_streaming_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -166,6 +189,7 @@ def test_path_params_update(self, client: Browserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.sessions.with_raw_response.update( id="", + project_id="projectId", status="REQUEST_RELEASE", ) @@ -207,7 +231,7 @@ def test_method_debug(self, client: Browserbase) -> None: session = client.sessions.debug( "id", ) - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize def test_raw_response_debug(self, client: Browserbase) -> None: @@ -218,7 +242,7 @@ def test_raw_response_debug(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize def test_streaming_response_debug(self, client: Browserbase) -> None: @@ -229,7 +253,7 @@ def test_streaming_response_debug(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -248,12 +272,15 @@ class TestAsyncSessions: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: - session = await async_client.sessions.create() + session = await async_client.sessions.create( + project_id="projectId", + ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.create( + project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -264,6 +291,19 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas "persist": True, }, "extension_id": "extensionId", + "fingerprint": { + "browsers": ["chrome"], + "devices": ["desktop"], + "http_version": "1", + "locales": ["string"], + "operating_systems": ["android"], + "screen": { + "max_height": 0, + "max_width": 0, + "min_height": 0, + "min_width": 0, + }, + }, "log_session": True, "os": "windows", "record_session": True, @@ -275,8 +315,17 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas }, extension_id="extensionId", keep_alive=True, - project_id="projectId", - proxies=True, + proxies=[ + { + "type": "browserbase", + "domain_pattern": "domainPattern", + "geolocation": { + "country": "xx", + "city": "city", + "state": "xx", + }, + } + ], region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -285,7 +334,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.sessions.with_raw_response.create() + response = await async_client.sessions.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -294,7 +345,9 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.sessions.with_streaming_response.create() as response: + async with async_client.sessions.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -345,42 +398,36 @@ async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> Non async def test_method_update(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.update( id="id", - status="REQUEST_RELEASE", - ) - assert_matches_type(Session, session, path=["response"]) - - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncBrowserbase) -> None: - session = await async_client.sessions.update( - id="id", - status="REQUEST_RELEASE", project_id="projectId", + status="REQUEST_RELEASE", ) - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None: response = await async_client.sessions.with_raw_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize async def test_streaming_response_update(self, async_client: AsyncBrowserbase) -> None: async with async_client.sessions.with_streaming_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -389,6 +436,7 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.sessions.with_raw_response.update( id="", + project_id="projectId", status="REQUEST_RELEASE", ) @@ -430,7 +478,7 @@ async def test_method_debug(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.debug( "id", ) - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -441,7 +489,7 @@ async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -452,7 +500,7 @@ async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index 71396d2..608cc1f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -864,7 +864,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.sessions.with_streaming_response.create().__enter__() + client.sessions.with_streaming_response.create(project_id="projectId").__enter__() assert _get_open_connections(client) == 0 @@ -874,7 +874,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.sessions.with_streaming_response.create().__enter__() + client.sessions.with_streaming_response.create(project_id="projectId").__enter__() assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -903,7 +903,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create() + response = client.sessions.with_raw_response.create(project_id="projectId") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -927,7 +927,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) + response = client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -950,7 +952,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) + response = client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1766,7 +1770,7 @@ async def test_retrying_timeout_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.sessions.with_streaming_response.create().__aenter__() + await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() assert _get_open_connections(async_client) == 0 @@ -1778,7 +1782,7 @@ async def test_retrying_status_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.sessions.with_streaming_response.create().__aenter__() + await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1807,7 +1811,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create() + response = await client.sessions.with_raw_response.create(project_id="projectId") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1831,7 +1835,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) + response = await client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1854,7 +1860,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) + response = await client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From 7ace9396d265afabebd57169e6a32dcedec3b0d4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:56:14 +0000 Subject: [PATCH 36/45] feat(api): manual updates --- .stats.yml | 8 +- README.md | 1 - api.md | 29 ++-- src/browserbase/resources/contexts.py | 104 ++++++++++++-- src/browserbase/resources/extensions.py | 27 ++-- src/browserbase/resources/projects.py | 32 ++--- .../resources/sessions/sessions.py | 86 ++++++------ src/browserbase/types/__init__.py | 13 +- ...ontext_retrieve_response.py => context.py} | 4 +- .../types/context_create_params.py | 4 +- ...ension_create_response.py => extension.py} | 4 +- .../types/extension_retrieve_response.py | 22 --- ...roject_retrieve_response.py => project.py} | 4 +- .../types/project_list_response.py | 27 +--- ...ect_usage_response.py => project_usage.py} | 4 +- ...{session_update_response.py => session.py} | 4 +- .../types/session_create_params.py | 92 +++++------- .../types/session_list_response.py | 58 +------- ...debug_response.py => session_live_urls.py} | 4 +- .../types/session_update_params.py | 12 +- src/browserbase/types/sessions/__init__.py | 2 + .../types/sessions/log_list_response.py | 48 +------ .../sessions/recording_retrieve_response.py | 26 +--- src/browserbase/types/sessions/session_log.py | 46 ++++++ .../types/sessions/session_recording.py | 24 ++++ tests/api_resources/test_contexts.py | 120 +++++++++++++--- tests/api_resources/test_extensions.py | 26 ++-- tests/api_resources/test_projects.py | 26 ++-- tests/api_resources/test_sessions.py | 132 ++++++------------ tests/test_client.py | 28 ++-- 30 files changed, 503 insertions(+), 514 deletions(-) rename src/browserbase/types/{context_retrieve_response.py => context.py} (84%) rename src/browserbase/types/{extension_create_response.py => extension.py} (85%) delete mode 100644 src/browserbase/types/extension_retrieve_response.py rename src/browserbase/types/{project_retrieve_response.py => project.py} (87%) rename src/browserbase/types/{project_usage_response.py => project_usage.py} (78%) rename src/browserbase/types/{session_update_response.py => session.py} (95%) rename src/browserbase/types/{session_debug_response.py => session_live_urls.py} (88%) create mode 100644 src/browserbase/types/sessions/session_log.py create mode 100644 src/browserbase/types/sessions/session_recording.py diff --git a/.stats.yml b/.stats.yml index b000c8c..9a94e54 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 18 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-be7a4aeebb1605262935b4b3ab446a95b1fad8a7d18098943dd548c8a486ef13.yml -openapi_spec_hash: 1c950a109f80140711e7ae2cf87fddad -config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b +configured_endpoints: 19 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-b92143ddb16135de4ff65ce8bcdfe9991d11c73570f42f07ea27e0da86209a44.yml +openapi_spec_hash: 16eb6e6c9687f01d2a791775b27dc315 +config_hash: b01d72cbe03bd762a73b05744086b2ec diff --git a/README.md b/README.md index 6863a76..b379eb7 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,6 @@ from browserbase import Browserbase client = Browserbase() session = client.sessions.create( - project_id="projectId", browser_settings={}, ) print(session.browser_settings) diff --git a/api.md b/api.md index 0145485..d2d26e5 100644 --- a/api.md +++ b/api.md @@ -3,27 +3,28 @@ Types: ```python -from browserbase.types import ContextCreateResponse, ContextRetrieveResponse, ContextUpdateResponse +from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse ``` Methods: - client.contexts.create(\*\*params) -> ContextCreateResponse -- client.contexts.retrieve(id) -> ContextRetrieveResponse +- client.contexts.retrieve(id) -> Context - client.contexts.update(id) -> ContextUpdateResponse +- client.contexts.delete(id) -> None # Extensions Types: ```python -from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse +from browserbase.types import Extension ``` Methods: -- client.extensions.create(\*\*params) -> ExtensionCreateResponse -- client.extensions.retrieve(id) -> ExtensionRetrieveResponse +- client.extensions.create(\*\*params) -> Extension +- client.extensions.retrieve(id) -> Extension - client.extensions.delete(id) -> None # Projects @@ -31,14 +32,14 @@ Methods: Types: ```python -from browserbase.types import ProjectRetrieveResponse, ProjectListResponse, ProjectUsageResponse +from browserbase.types import Project, ProjectUsage, ProjectListResponse ``` Methods: -- client.projects.retrieve(id) -> ProjectRetrieveResponse +- client.projects.retrieve(id) -> Project - client.projects.list() -> ProjectListResponse -- client.projects.usage(id) -> ProjectUsageResponse +- client.projects.usage(id) -> ProjectUsage # Sessions @@ -46,11 +47,11 @@ Types: ```python from browserbase.types import ( + Session, + SessionLiveURLs, SessionCreateResponse, SessionRetrieveResponse, - SessionUpdateResponse, SessionListResponse, - SessionDebugResponse, ) ``` @@ -58,9 +59,9 @@ Methods: - client.sessions.create(\*\*params) -> SessionCreateResponse - client.sessions.retrieve(id) -> SessionRetrieveResponse -- client.sessions.update(id, \*\*params) -> SessionUpdateResponse +- client.sessions.update(id, \*\*params) -> Session - client.sessions.list(\*\*params) -> SessionListResponse -- client.sessions.debug(id) -> SessionDebugResponse +- client.sessions.debug(id) -> SessionLiveURLs ## Downloads @@ -73,7 +74,7 @@ Methods: Types: ```python -from browserbase.types.sessions import LogListResponse +from browserbase.types.sessions import SessionLog, LogListResponse ``` Methods: @@ -85,7 +86,7 @@ Methods: Types: ```python -from browserbase.types.sessions import RecordingRetrieveResponse +from browserbase.types.sessions import SessionRecording, RecordingRetrieveResponse ``` Methods: diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index d2bb416..03af85f 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -5,7 +5,7 @@ import httpx from ..types import context_create_params -from .._types import Body, Query, Headers, NotGiven, not_given +from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -16,9 +16,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.context import Context from ..types.context_create_response import ContextCreateResponse from ..types.context_update_response import ContextUpdateResponse -from ..types.context_retrieve_response import ContextRetrieveResponse __all__ = ["ContextsResource", "AsyncContextsResource"] @@ -46,7 +46,7 @@ def with_streaming_response(self) -> ContextsResourceWithStreamingResponse: def create( self, *, - project_id: str, + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -89,9 +89,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ContextRetrieveResponse: + ) -> Context: """ - Get a Context + Context Args: extra_headers: Send extra headers @@ -109,7 +109,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ContextRetrieveResponse, + cast_to=Context, ) def update( @@ -124,7 +124,7 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update a Context + Update Context Args: extra_headers: Send extra headers @@ -145,6 +145,40 @@ def update( cast_to=ContextUpdateResponse, ) + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Context + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/v1/contexts/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class AsyncContextsResource(AsyncAPIResource): @cached_property @@ -169,7 +203,7 @@ def with_streaming_response(self) -> AsyncContextsResourceWithStreamingResponse: async def create( self, *, - project_id: str, + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -212,9 +246,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ContextRetrieveResponse: + ) -> Context: """ - Get a Context + Context Args: extra_headers: Send extra headers @@ -232,7 +266,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ContextRetrieveResponse, + cast_to=Context, ) async def update( @@ -247,7 +281,7 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update a Context + Update Context Args: extra_headers: Send extra headers @@ -268,6 +302,40 @@ async def update( cast_to=ContextUpdateResponse, ) + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete Context + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/v1/contexts/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + class ContextsResourceWithRawResponse: def __init__(self, contexts: ContextsResource) -> None: @@ -282,6 +350,9 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_raw_response_wrapper( contexts.update, ) + self.delete = to_raw_response_wrapper( + contexts.delete, + ) class AsyncContextsResourceWithRawResponse: @@ -297,6 +368,9 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_raw_response_wrapper( contexts.update, ) + self.delete = async_to_raw_response_wrapper( + contexts.delete, + ) class ContextsResourceWithStreamingResponse: @@ -312,6 +386,9 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_streamed_response_wrapper( contexts.update, ) + self.delete = to_streamed_response_wrapper( + contexts.delete, + ) class AsyncContextsResourceWithStreamingResponse: @@ -327,3 +404,6 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_streamed_response_wrapper( contexts.update, ) + self.delete = async_to_streamed_response_wrapper( + contexts.delete, + ) diff --git a/src/browserbase/resources/extensions.py b/src/browserbase/resources/extensions.py index 21d06e7..882a495 100644 --- a/src/browserbase/resources/extensions.py +++ b/src/browserbase/resources/extensions.py @@ -18,8 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.extension_create_response import ExtensionCreateResponse -from ..types.extension_retrieve_response import ExtensionRetrieveResponse +from ..types.extension import Extension __all__ = ["ExtensionsResource", "AsyncExtensionsResource"] @@ -54,7 +53,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionCreateResponse: + ) -> Extension: """ Upload an Extension @@ -80,7 +79,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionCreateResponse, + cast_to=Extension, ) def retrieve( @@ -93,9 +92,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionRetrieveResponse: + ) -> Extension: """ - Get an Extension + Extension Args: extra_headers: Send extra headers @@ -113,7 +112,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionRetrieveResponse, + cast_to=Extension, ) def delete( @@ -128,7 +127,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete an Extension + Delete Extension Args: extra_headers: Send extra headers @@ -181,7 +180,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionCreateResponse: + ) -> Extension: """ Upload an Extension @@ -207,7 +206,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionCreateResponse, + cast_to=Extension, ) async def retrieve( @@ -220,9 +219,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionRetrieveResponse: + ) -> Extension: """ - Get an Extension + Extension Args: extra_headers: Send extra headers @@ -240,7 +239,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionRetrieveResponse, + cast_to=Extension, ) async def delete( @@ -255,7 +254,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete an Extension + Delete Extension Args: extra_headers: Send extra headers diff --git a/src/browserbase/resources/projects.py b/src/browserbase/resources/projects.py index 62c28af..a6ae633 100644 --- a/src/browserbase/resources/projects.py +++ b/src/browserbase/resources/projects.py @@ -14,9 +14,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.project import Project +from ..types.project_usage import ProjectUsage from ..types.project_list_response import ProjectListResponse -from ..types.project_usage_response import ProjectUsageResponse -from ..types.project_retrieve_response import ProjectRetrieveResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -51,9 +51,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectRetrieveResponse: + ) -> Project: """ - Get a Project + Project Args: extra_headers: Send extra headers @@ -71,7 +71,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectRetrieveResponse, + cast_to=Project, ) def list( @@ -84,7 +84,7 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List Projects""" + """List projects""" return self._get( "/v1/projects", options=make_request_options( @@ -103,9 +103,9 @@ def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsageResponse: + ) -> ProjectUsage: """ - Get Project Usage + Project Usage Args: extra_headers: Send extra headers @@ -123,7 +123,7 @@ def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsageResponse, + cast_to=ProjectUsage, ) @@ -157,9 +157,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectRetrieveResponse: + ) -> Project: """ - Get a Project + Project Args: extra_headers: Send extra headers @@ -177,7 +177,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectRetrieveResponse, + cast_to=Project, ) async def list( @@ -190,7 +190,7 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List Projects""" + """List projects""" return await self._get( "/v1/projects", options=make_request_options( @@ -209,9 +209,9 @@ async def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsageResponse: + ) -> ProjectUsage: """ - Get Project Usage + Project Usage Args: extra_headers: Send extra headers @@ -229,7 +229,7 @@ async def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsageResponse, + cast_to=ProjectUsage, ) diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index ceaaeb8..09fd15a 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -51,10 +51,10 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options +from ...types.session import Session +from ...types.session_live_urls import SessionLiveURLs from ...types.session_list_response import SessionListResponse -from ...types.session_debug_response import SessionDebugResponse from ...types.session_create_response import SessionCreateResponse -from ...types.session_update_response import SessionUpdateResponse from ...types.session_retrieve_response import SessionRetrieveResponse __all__ = ["SessionsResource", "AsyncSessionsResource"] @@ -99,11 +99,11 @@ def with_streaming_response(self) -> SessionsResourceWithStreamingResponse: def create( self, *, - project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, - proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, + project_id: str | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -117,17 +117,17 @@ def create( """Create a Session Args: - project_id: The Project ID. + extension_id: The uploaded Extension ID. - Can be found in - [Settings](https://www.browserbase.com/settings). - - extension_id: The uploaded Extension ID. See + See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -151,10 +151,10 @@ def create( "/v1/sessions", body=maybe_transform( { - "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, + "project_id": project_id, "proxies": proxies, "region": region, "api_timeout": api_timeout, @@ -180,7 +180,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Get a Session + Session Args: extra_headers: Send extra headers @@ -205,26 +205,25 @@ def update( self, id: str, *, - project_id: str, status: Literal["REQUEST_RELEASE"], + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionUpdateResponse: - """Update a Session + ) -> Session: + """ + Update Session Args: - project_id: The Project ID. - - Can be found in - [Settings](https://www.browserbase.com/settings). - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -239,15 +238,15 @@ def update( f"/v1/sessions/{id}", body=maybe_transform( { - "project_id": project_id, "status": status, + "project_id": project_id, }, session_update_params.SessionUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionUpdateResponse, + cast_to=Session, ) def list( @@ -307,7 +306,7 @@ def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionDebugResponse: + ) -> SessionLiveURLs: """ Session Live URLs @@ -327,7 +326,7 @@ def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionDebugResponse, + cast_to=SessionLiveURLs, ) @@ -370,11 +369,11 @@ def with_streaming_response(self) -> AsyncSessionsResourceWithStreamingResponse: async def create( self, *, - project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, - proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, + project_id: str | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, @@ -388,17 +387,17 @@ async def create( """Create a Session Args: - project_id: The Project ID. + extension_id: The uploaded Extension ID. - Can be found in - [Settings](https://www.browserbase.com/settings). - - extension_id: The uploaded Extension ID. See + See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -422,10 +421,10 @@ async def create( "/v1/sessions", body=await async_maybe_transform( { - "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, + "project_id": project_id, "proxies": proxies, "region": region, "api_timeout": api_timeout, @@ -451,7 +450,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Get a Session + Session Args: extra_headers: Send extra headers @@ -476,26 +475,25 @@ async def update( self, id: str, *, - project_id: str, status: Literal["REQUEST_RELEASE"], + project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionUpdateResponse: - """Update a Session + ) -> Session: + """ + Update Session Args: - project_id: The Project ID. - - Can be found in - [Settings](https://www.browserbase.com/settings). - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges. + project_id: The Project ID. Can be found in + [Settings](https://www.browserbase.com/settings). + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -510,15 +508,15 @@ async def update( f"/v1/sessions/{id}", body=await async_maybe_transform( { - "project_id": project_id, "status": status, + "project_id": project_id, }, session_update_params.SessionUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionUpdateResponse, + cast_to=Session, ) async def list( @@ -578,7 +576,7 @@ async def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionDebugResponse: + ) -> SessionLiveURLs: """ Session Live URLs @@ -598,7 +596,7 @@ async def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionDebugResponse, + cast_to=SessionLiveURLs, ) diff --git a/src/browserbase/types/__init__.py b/src/browserbase/types/__init__.py index 20e2f90..4dd85dd 100644 --- a/src/browserbase/types/__init__.py +++ b/src/browserbase/types/__init__.py @@ -2,21 +2,20 @@ from __future__ import annotations +from .context import Context as Context +from .project import Project as Project +from .session import Session as Session +from .extension import Extension as Extension +from .project_usage import ProjectUsage as ProjectUsage +from .session_live_urls import SessionLiveURLs as SessionLiveURLs from .session_list_params import SessionListParams as SessionListParams from .context_create_params import ContextCreateParams as ContextCreateParams from .project_list_response import ProjectListResponse as ProjectListResponse from .session_create_params import SessionCreateParams as SessionCreateParams from .session_list_response import SessionListResponse as SessionListResponse from .session_update_params import SessionUpdateParams as SessionUpdateParams -from .project_usage_response import ProjectUsageResponse as ProjectUsageResponse -from .session_debug_response import SessionDebugResponse as SessionDebugResponse from .context_create_response import ContextCreateResponse as ContextCreateResponse from .context_update_response import ContextUpdateResponse as ContextUpdateResponse from .extension_create_params import ExtensionCreateParams as ExtensionCreateParams from .session_create_response import SessionCreateResponse as SessionCreateResponse -from .session_update_response import SessionUpdateResponse as SessionUpdateResponse -from .context_retrieve_response import ContextRetrieveResponse as ContextRetrieveResponse -from .extension_create_response import ExtensionCreateResponse as ExtensionCreateResponse -from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .session_retrieve_response import SessionRetrieveResponse as SessionRetrieveResponse -from .extension_retrieve_response import ExtensionRetrieveResponse as ExtensionRetrieveResponse diff --git a/src/browserbase/types/context_retrieve_response.py b/src/browserbase/types/context.py similarity index 84% rename from src/browserbase/types/context_retrieve_response.py rename to src/browserbase/types/context.py index c2cd692..cb5c32f 100644 --- a/src/browserbase/types/context_retrieve_response.py +++ b/src/browserbase/types/context.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["ContextRetrieveResponse"] +__all__ = ["Context"] -class ContextRetrieveResponse(BaseModel): +class Context(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/context_create_params.py b/src/browserbase/types/context_create_params.py index 75cd1fc..66c6c46 100644 --- a/src/browserbase/types/context_create_params.py +++ b/src/browserbase/types/context_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -10,7 +10,7 @@ class ContextCreateParams(TypedDict, total=False): - project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] + project_id: Annotated[str, PropertyInfo(alias="projectId")] """The Project ID. Can be found in [Settings](https://www.browserbase.com/settings). diff --git a/src/browserbase/types/extension_create_response.py b/src/browserbase/types/extension.py similarity index 85% rename from src/browserbase/types/extension_create_response.py rename to src/browserbase/types/extension.py index d2b74f4..94582c3 100644 --- a/src/browserbase/types/extension_create_response.py +++ b/src/browserbase/types/extension.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["ExtensionCreateResponse"] +__all__ = ["Extension"] -class ExtensionCreateResponse(BaseModel): +class Extension(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension_retrieve_response.py b/src/browserbase/types/extension_retrieve_response.py deleted file mode 100644 index c786348..0000000 --- a/src/browserbase/types/extension_retrieve_response.py +++ /dev/null @@ -1,22 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["ExtensionRetrieveResponse"] - - -class ExtensionRetrieveResponse(BaseModel): - id: str - - created_at: datetime = FieldInfo(alias="createdAt") - - file_name: str = FieldInfo(alias="fileName") - - project_id: str = FieldInfo(alias="projectId") - """The Project ID linked to the uploaded Extension.""" - - updated_at: datetime = FieldInfo(alias="updatedAt") diff --git a/src/browserbase/types/project_retrieve_response.py b/src/browserbase/types/project.py similarity index 87% rename from src/browserbase/types/project_retrieve_response.py rename to src/browserbase/types/project.py index 7812667..dc3cf33 100644 --- a/src/browserbase/types/project_retrieve_response.py +++ b/src/browserbase/types/project.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["ProjectRetrieveResponse"] +__all__ = ["Project"] -class ProjectRetrieveResponse(BaseModel): +class Project(BaseModel): id: str concurrency: int diff --git a/src/browserbase/types/project_list_response.py b/src/browserbase/types/project_list_response.py index e364b52..2d05a23 100644 --- a/src/browserbase/types/project_list_response.py +++ b/src/browserbase/types/project_list_response.py @@ -1,31 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List -from datetime import datetime from typing_extensions import TypeAlias -from pydantic import Field as FieldInfo +from .project import Project -from .._models import BaseModel +__all__ = ["ProjectListResponse"] -__all__ = ["ProjectListResponse", "ProjectListResponseItem"] - - -class ProjectListResponseItem(BaseModel): - id: str - - concurrency: int - """The maximum number of sessions that this project can run concurrently.""" - - created_at: datetime = FieldInfo(alias="createdAt") - - default_timeout: int = FieldInfo(alias="defaultTimeout") - - name: str - - owner_id: str = FieldInfo(alias="ownerId") - - updated_at: datetime = FieldInfo(alias="updatedAt") - - -ProjectListResponse: TypeAlias = List[ProjectListResponseItem] +ProjectListResponse: TypeAlias = List[Project] diff --git a/src/browserbase/types/project_usage_response.py b/src/browserbase/types/project_usage.py similarity index 78% rename from src/browserbase/types/project_usage_response.py rename to src/browserbase/types/project_usage.py index b52fccf..c8a03f5 100644 --- a/src/browserbase/types/project_usage_response.py +++ b/src/browserbase/types/project_usage.py @@ -4,10 +4,10 @@ from .._models import BaseModel -__all__ = ["ProjectUsageResponse"] +__all__ = ["ProjectUsage"] -class ProjectUsageResponse(BaseModel): +class ProjectUsage(BaseModel): browser_minutes: int = FieldInfo(alias="browserMinutes") proxy_bytes: int = FieldInfo(alias="proxyBytes") diff --git a/src/browserbase/types/session_update_response.py b/src/browserbase/types/session.py similarity index 95% rename from src/browserbase/types/session_update_response.py rename to src/browserbase/types/session.py index 67a1371..16450e2 100644 --- a/src/browserbase/types/session_update_response.py +++ b/src/browserbase/types/session.py @@ -8,10 +8,10 @@ from .._models import BaseModel -__all__ = ["SessionUpdateResponse"] +__all__ = ["Session"] -class SessionUpdateResponse(BaseModel): +class Session(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 2ba3640..33dd6fd 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -2,33 +2,25 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable +from typing import Dict, Union, Iterable from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict -from .._types import SequenceNotStr from .._utils import PropertyInfo __all__ = [ "SessionCreateParams", "BrowserSettings", "BrowserSettingsContext", - "BrowserSettingsFingerprint", - "BrowserSettingsFingerprintScreen", "BrowserSettingsViewport", - "ProxiesUnionMember0", - "ProxiesUnionMember0UnionMember0", - "ProxiesUnionMember0UnionMember0Geolocation", - "ProxiesUnionMember0UnionMember1", + "ProxiesUnionMember1", + "ProxiesUnionMember1BrowserbaseProxyConfig", + "ProxiesUnionMember1BrowserbaseProxyConfigGeolocation", + "ProxiesUnionMember1ExternalProxyConfig", + "ProxiesUnionMember1NoneProxyConfig", ] class SessionCreateParams(TypedDict, total=False): - project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] - """The Project ID. - - Can be found in [Settings](https://www.browserbase.com/settings). - """ - browser_settings: Annotated[BrowserSettings, PropertyInfo(alias="browserSettings")] extension_id: Annotated[str, PropertyInfo(alias="extensionId")] @@ -43,7 +35,13 @@ class SessionCreateParams(TypedDict, total=False): Available on the Hobby Plan and above. """ - proxies: Union[Iterable[ProxiesUnionMember0], bool] + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """The Project ID. + + Can be found in [Settings](https://www.browserbase.com/settings). + """ + + proxies: Union[bool, Iterable[ProxiesUnionMember1]] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -74,42 +72,10 @@ class BrowserSettingsContext(TypedDict, total=False): """Whether or not to persist the context after browsing. Defaults to `false`.""" -class BrowserSettingsFingerprintScreen(TypedDict, total=False): - max_height: Annotated[int, PropertyInfo(alias="maxHeight")] - - max_width: Annotated[int, PropertyInfo(alias="maxWidth")] - - min_height: Annotated[int, PropertyInfo(alias="minHeight")] - - min_width: Annotated[int, PropertyInfo(alias="minWidth")] - - -class BrowserSettingsFingerprint(TypedDict, total=False): - """ - See usage examples [on the Stealth Mode page](/features/stealth-mode#fingerprinting) - """ - - browsers: List[Literal["chrome", "edge", "firefox", "safari"]] - - devices: List[Literal["desktop", "mobile"]] - - http_version: Annotated[Literal["1", "2"], PropertyInfo(alias="httpVersion")] - - locales: SequenceNotStr[str] - - operating_systems: Annotated[ - List[Literal["android", "ios", "linux", "macos", "windows"]], PropertyInfo(alias="operatingSystems") - ] - - screen: BrowserSettingsFingerprintScreen - - class BrowserSettingsViewport(TypedDict, total=False): height: int - """The height of the browser.""" width: int - """The width of the browser.""" class BrowserSettings(TypedDict, total=False): @@ -139,12 +105,6 @@ class BrowserSettings(TypedDict, total=False): See [Upload Extension](/reference/api/upload-an-extension). """ - fingerprint: BrowserSettingsFingerprint - """ - See usage examples - [on the Stealth Mode page](/features/stealth-mode#fingerprinting) - """ - log_session: Annotated[bool, PropertyInfo(alias="logSession")] """Enable or disable session logging. Defaults to `true`.""" @@ -163,8 +123,8 @@ class BrowserSettings(TypedDict, total=False): viewport: BrowserSettingsViewport -class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): - """Geographic location for the proxy. Optional.""" +class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): + """Configuration for geolocation""" country: Required[str] """Country code in ISO 3166-1 alpha-2 format""" @@ -176,7 +136,7 @@ class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): """US state code (2 characters). Must also specify US as the country. Optional.""" -class ProxiesUnionMember0UnionMember0(TypedDict, total=False): +class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): type: Required[Literal["browserbase"]] """Type of proxy. @@ -189,11 +149,11 @@ class ProxiesUnionMember0UnionMember0(TypedDict, total=False): If omitted, defaults to all domains. Optional. """ - geolocation: ProxiesUnionMember0UnionMember0Geolocation - """Geographic location for the proxy. Optional.""" + geolocation: ProxiesUnionMember1BrowserbaseProxyConfigGeolocation + """Configuration for geolocation""" -class ProxiesUnionMember0UnionMember1(TypedDict, total=False): +class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): server: Required[str] """Server URL for external proxy. Required.""" @@ -213,4 +173,16 @@ class ProxiesUnionMember0UnionMember1(TypedDict, total=False): """Username for external proxy authentication. Optional.""" -ProxiesUnionMember0: TypeAlias = Union[ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1] +class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): + domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] + """Domain pattern for which site should have proxies disabled.""" + + type: Required[Literal["none"]] + """Type of proxy. Use 'none' to disable proxy for matching domains.""" + + +ProxiesUnionMember1: TypeAlias = Union[ + ProxiesUnionMember1BrowserbaseProxyConfig, + ProxiesUnionMember1ExternalProxyConfig, + ProxiesUnionMember1NoneProxyConfig, +] diff --git a/src/browserbase/types/session_list_response.py b/src/browserbase/types/session_list_response.py index 4c1bd88..ca162dd 100644 --- a/src/browserbase/types/session_list_response.py +++ b/src/browserbase/types/session_list_response.py @@ -1,58 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional -from datetime import datetime -from typing_extensions import Literal, TypeAlias +from typing import List +from typing_extensions import TypeAlias -from pydantic import Field as FieldInfo +from .session import Session -from .._models import BaseModel +__all__ = ["SessionListResponse"] -__all__ = ["SessionListResponse", "SessionListResponseItem"] - - -class SessionListResponseItem(BaseModel): - id: str - - created_at: datetime = FieldInfo(alias="createdAt") - - expires_at: datetime = FieldInfo(alias="expiresAt") - - keep_alive: bool = FieldInfo(alias="keepAlive") - """Indicates if the Session was created to be kept alive upon disconnections""" - - project_id: str = FieldInfo(alias="projectId") - """The Project ID linked to the Session.""" - - proxy_bytes: int = FieldInfo(alias="proxyBytes") - """Bytes used via the [Proxy](/features/stealth-mode#proxies-and-residential-ips)""" - - region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] - """The region where the Session is running.""" - - started_at: datetime = FieldInfo(alias="startedAt") - - status: Literal["RUNNING", "ERROR", "TIMED_OUT", "COMPLETED"] - - updated_at: datetime = FieldInfo(alias="updatedAt") - - avg_cpu_usage: Optional[int] = FieldInfo(alias="avgCpuUsage", default=None) - """CPU used by the Session""" - - context_id: Optional[str] = FieldInfo(alias="contextId", default=None) - """Optional. The Context linked to the Session.""" - - ended_at: Optional[datetime] = FieldInfo(alias="endedAt", default=None) - - memory_usage: Optional[int] = FieldInfo(alias="memoryUsage", default=None) - """Memory used by the Session""" - - user_metadata: Optional[Dict[str, object]] = FieldInfo(alias="userMetadata", default=None) - """Arbitrary user metadata to attach to the session. - - To learn more about user metadata, see - [User Metadata](/features/sessions#user-metadata). - """ - - -SessionListResponse: TypeAlias = List[SessionListResponseItem] +SessionListResponse: TypeAlias = List[Session] diff --git a/src/browserbase/types/session_debug_response.py b/src/browserbase/types/session_live_urls.py similarity index 88% rename from src/browserbase/types/session_debug_response.py rename to src/browserbase/types/session_live_urls.py index 9cee7a7..3c7ba32 100644 --- a/src/browserbase/types/session_debug_response.py +++ b/src/browserbase/types/session_live_urls.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["SessionDebugResponse", "Page"] +__all__ = ["SessionLiveURLs", "Page"] class Page(BaseModel): @@ -23,7 +23,7 @@ class Page(BaseModel): url: str -class SessionDebugResponse(BaseModel): +class SessionLiveURLs(BaseModel): debugger_fullscreen_url: str = FieldInfo(alias="debuggerFullscreenUrl") debugger_url: str = FieldInfo(alias="debuggerUrl") diff --git a/src/browserbase/types/session_update_params.py b/src/browserbase/types/session_update_params.py index 66dcd35..71c589d 100644 --- a/src/browserbase/types/session_update_params.py +++ b/src/browserbase/types/session_update_params.py @@ -10,14 +10,14 @@ class SessionUpdateParams(TypedDict, total=False): - project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] - """The Project ID. - - Can be found in [Settings](https://www.browserbase.com/settings). - """ - status: Required[Literal["REQUEST_RELEASE"]] """Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges. """ + + project_id: Annotated[str, PropertyInfo(alias="projectId")] + """The Project ID. + + Can be found in [Settings](https://www.browserbase.com/settings). + """ diff --git a/src/browserbase/types/sessions/__init__.py b/src/browserbase/types/sessions/__init__.py index 69d5470..0cef6b1 100644 --- a/src/browserbase/types/sessions/__init__.py +++ b/src/browserbase/types/sessions/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations +from .session_log import SessionLog as SessionLog from .log_list_response import LogListResponse as LogListResponse +from .session_recording import SessionRecording as SessionRecording from .upload_create_params import UploadCreateParams as UploadCreateParams from .upload_create_response import UploadCreateResponse as UploadCreateResponse from .recording_retrieve_response import RecordingRetrieveResponse as RecordingRetrieveResponse diff --git a/src/browserbase/types/sessions/log_list_response.py b/src/browserbase/types/sessions/log_list_response.py index efd848a..2b325a8 100644 --- a/src/browserbase/types/sessions/log_list_response.py +++ b/src/browserbase/types/sessions/log_list_response.py @@ -1,50 +1,10 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import List from typing_extensions import TypeAlias -from pydantic import Field as FieldInfo +from .session_log import SessionLog -from ..._models import BaseModel +__all__ = ["LogListResponse"] -__all__ = ["LogListResponse", "LogListResponseItem", "LogListResponseItemRequest", "LogListResponseItemResponse"] - - -class LogListResponseItemRequest(BaseModel): - params: Dict[str, object] - - raw_body: str = FieldInfo(alias="rawBody") - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class LogListResponseItemResponse(BaseModel): - raw_body: str = FieldInfo(alias="rawBody") - - result: Dict[str, object] - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class LogListResponseItem(BaseModel): - method: str - - page_id: int = FieldInfo(alias="pageId") - - session_id: str = FieldInfo(alias="sessionId") - - frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) - - loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) - - request: Optional[LogListResponseItemRequest] = None - - response: Optional[LogListResponseItemResponse] = None - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -LogListResponse: TypeAlias = List[LogListResponseItem] +LogListResponse: TypeAlias = List[SessionLog] diff --git a/src/browserbase/types/sessions/recording_retrieve_response.py b/src/browserbase/types/sessions/recording_retrieve_response.py index d3613b8..951969b 100644 --- a/src/browserbase/types/sessions/recording_retrieve_response.py +++ b/src/browserbase/types/sessions/recording_retrieve_response.py @@ -1,28 +1,10 @@ # 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 -from pydantic import Field as FieldInfo +from .session_recording import SessionRecording -from ..._models import BaseModel +__all__ = ["RecordingRetrieveResponse"] -__all__ = ["RecordingRetrieveResponse", "RecordingRetrieveResponseItem"] - - -class RecordingRetrieveResponseItem(BaseModel): - data: Dict[str, object] - """ - See - [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). - """ - - session_id: str = FieldInfo(alias="sessionId") - - timestamp: int - """milliseconds that have elapsed since the UNIX epoch""" - - type: int - - -RecordingRetrieveResponse: TypeAlias = List[RecordingRetrieveResponseItem] +RecordingRetrieveResponse: TypeAlias = List[SessionRecording] diff --git a/src/browserbase/types/sessions/session_log.py b/src/browserbase/types/sessions/session_log.py new file mode 100644 index 0000000..428f518 --- /dev/null +++ b/src/browserbase/types/sessions/session_log.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["SessionLog", "Request", "Response"] + + +class Request(BaseModel): + params: Dict[str, object] + + raw_body: str = FieldInfo(alias="rawBody") + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class Response(BaseModel): + raw_body: str = FieldInfo(alias="rawBody") + + result: Dict[str, object] + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class SessionLog(BaseModel): + method: str + + page_id: int = FieldInfo(alias="pageId") + + session_id: str = FieldInfo(alias="sessionId") + + frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) + + loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) + + request: Optional[Request] = None + + response: Optional[Response] = None + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" diff --git a/src/browserbase/types/sessions/session_recording.py b/src/browserbase/types/sessions/session_recording.py new file mode 100644 index 0000000..c847137 --- /dev/null +++ b/src/browserbase/types/sessions/session_recording.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["SessionRecording"] + + +class SessionRecording(BaseModel): + data: Dict[str, object] + """ + See + [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). + """ + + session_id: str = FieldInfo(alias="sessionId") + + timestamp: int + """milliseconds that have elapsed since the UNIX epoch""" + + type: int diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 4ad2773..31fb97d 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -9,11 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import ( - ContextCreateResponse, - ContextUpdateResponse, - ContextRetrieveResponse, -) +from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,6 +19,11 @@ class TestContexts: @parametrize def test_method_create(self, client: Browserbase) -> None: + context = client.contexts.create() + assert_matches_type(ContextCreateResponse, context, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Browserbase) -> None: context = client.contexts.create( project_id="projectId", ) @@ -30,9 +31,7 @@ def test_method_create(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.create( - project_id="projectId", - ) + response = client.contexts.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -41,9 +40,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.create( - project_id="projectId", - ) as response: + with client.contexts.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -57,7 +54,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: context = client.contexts.retrieve( "id", ) - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -68,7 +65,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -79,7 +76,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -128,6 +125,44 @@ def test_path_params_update(self, client: Browserbase) -> None: "", ) + @parametrize + def test_method_delete(self, client: Browserbase) -> None: + context = client.contexts.delete( + "id", + ) + assert context is None + + @parametrize + def test_raw_response_delete(self, client: Browserbase) -> None: + response = client.contexts.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + context = response.parse() + assert context is None + + @parametrize + def test_streaming_response_delete(self, client: Browserbase) -> None: + with client.contexts.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + context = response.parse() + assert context is None + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Browserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.contexts.with_raw_response.delete( + "", + ) + class TestAsyncContexts: parametrize = pytest.mark.parametrize( @@ -136,6 +171,11 @@ class TestAsyncContexts: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: + context = await async_client.contexts.create() + assert_matches_type(ContextCreateResponse, context, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.create( project_id="projectId", ) @@ -143,9 +183,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.create( - project_id="projectId", - ) + response = await async_client.contexts.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -154,9 +192,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.create( - project_id="projectId", - ) as response: + async with async_client.contexts.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -170,7 +206,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.retrieve( "id", ) - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -181,7 +217,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -192,7 +228,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(ContextRetrieveResponse, context, path=["response"]) + assert_matches_type(Context, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -240,3 +276,41 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: await async_client.contexts.with_raw_response.update( "", ) + + @parametrize + async def test_method_delete(self, async_client: AsyncBrowserbase) -> None: + context = await async_client.contexts.delete( + "id", + ) + assert context is None + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncBrowserbase) -> None: + response = await async_client.contexts.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + context = await response.parse() + assert context is None + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncBrowserbase) -> None: + async with async_client.contexts.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + context = await response.parse() + assert context is None + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncBrowserbase) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.contexts.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py index e32ae9b..6b6a018 100644 --- a/tests/api_resources/test_extensions.py +++ b/tests/api_resources/test_extensions.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse +from browserbase.types import Extension base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_create(self, client: Browserbase) -> None: extension = client.extensions.create( file=b"raw file contents", ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_raw_response_create(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_create(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: extension = client.extensions.retrieve( "id", ) - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -64,7 +64,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -75,7 +75,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -135,7 +135,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.create( file=b"raw file contents", ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -146,7 +146,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -157,7 +157,7 @@ async def test_streaming_response_create(self, async_client: AsyncBrowserbase) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -166,7 +166,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.retrieve( "id", ) - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -177,7 +177,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -188,7 +188,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) + assert_matches_type(Extension, extension, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index 0d8e3c9..c8241bf 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import ProjectListResponse, ProjectUsageResponse, ProjectRetrieveResponse +from browserbase.types import Project, ProjectUsage, ProjectListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: project = client.projects.retrieve( "id", ) - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -85,7 +85,7 @@ def test_method_usage(self, client: Browserbase) -> None: project = client.projects.usage( "id", ) - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize def test_raw_response_usage(self, client: Browserbase) -> None: @@ -96,7 +96,7 @@ def test_raw_response_usage(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize def test_streaming_response_usage(self, client: Browserbase) -> None: @@ -107,7 +107,7 @@ def test_streaming_response_usage(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -129,7 +129,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.retrieve( "id", ) - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -140,7 +140,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -151,7 +151,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) + assert_matches_type(Project, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -192,7 +192,7 @@ async def test_method_usage(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.usage( "id", ) - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -203,7 +203,7 @@ async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) @parametrize async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -214,7 +214,7 @@ async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsageResponse, project, path=["response"]) + assert_matches_type(ProjectUsage, project, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 7a16f64..6a21aa8 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -10,10 +10,10 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type from browserbase.types import ( + Session, + SessionLiveURLs, SessionListResponse, - SessionDebugResponse, SessionCreateResponse, - SessionUpdateResponse, SessionRetrieveResponse, ) @@ -25,15 +25,12 @@ class TestSessions: @parametrize def test_method_create(self, client: Browserbase) -> None: - session = client.sessions.create( - project_id="projectId", - ) + session = client.sessions.create() assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Browserbase) -> None: session = client.sessions.create( - project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -44,19 +41,6 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: "persist": True, }, "extension_id": "extensionId", - "fingerprint": { - "browsers": ["chrome"], - "devices": ["desktop"], - "http_version": "1", - "locales": ["string"], - "operating_systems": ["android"], - "screen": { - "max_height": 0, - "max_width": 0, - "min_height": 0, - "min_width": 0, - }, - }, "log_session": True, "os": "windows", "record_session": True, @@ -68,17 +52,8 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: }, extension_id="extensionId", keep_alive=True, - proxies=[ - { - "type": "browserbase", - "domain_pattern": "domainPattern", - "geolocation": { - "country": "xx", - "city": "city", - "state": "xx", - }, - } - ], + project_id="projectId", + proxies=True, region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -87,9 +62,7 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.sessions.with_raw_response.create( - project_id="projectId", - ) + response = client.sessions.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -98,9 +71,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.sessions.with_streaming_response.create( - project_id="projectId", - ) as response: + with client.sessions.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -151,36 +122,42 @@ def test_path_params_retrieve(self, client: Browserbase) -> None: def test_method_update(self, client: Browserbase) -> None: session = client.sessions.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) + + @parametrize + def test_method_update_with_all_params(self, client: Browserbase) -> None: + session = client.sessions.update( + id="id", + status="REQUEST_RELEASE", + project_id="projectId", + ) + assert_matches_type(Session, session, path=["response"]) @parametrize def test_raw_response_update(self, client: Browserbase) -> None: response = client.sessions.with_raw_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize def test_streaming_response_update(self, client: Browserbase) -> None: with client.sessions.with_streaming_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -189,7 +166,6 @@ def test_path_params_update(self, client: Browserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.sessions.with_raw_response.update( id="", - project_id="projectId", status="REQUEST_RELEASE", ) @@ -231,7 +207,7 @@ def test_method_debug(self, client: Browserbase) -> None: session = client.sessions.debug( "id", ) - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize def test_raw_response_debug(self, client: Browserbase) -> None: @@ -242,7 +218,7 @@ def test_raw_response_debug(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize def test_streaming_response_debug(self, client: Browserbase) -> None: @@ -253,7 +229,7 @@ def test_streaming_response_debug(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -272,15 +248,12 @@ class TestAsyncSessions: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: - session = await async_client.sessions.create( - project_id="projectId", - ) + session = await async_client.sessions.create() assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.create( - project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -291,19 +264,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas "persist": True, }, "extension_id": "extensionId", - "fingerprint": { - "browsers": ["chrome"], - "devices": ["desktop"], - "http_version": "1", - "locales": ["string"], - "operating_systems": ["android"], - "screen": { - "max_height": 0, - "max_width": 0, - "min_height": 0, - "min_width": 0, - }, - }, "log_session": True, "os": "windows", "record_session": True, @@ -315,17 +275,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas }, extension_id="extensionId", keep_alive=True, - proxies=[ - { - "type": "browserbase", - "domain_pattern": "domainPattern", - "geolocation": { - "country": "xx", - "city": "city", - "state": "xx", - }, - } - ], + project_id="projectId", + proxies=True, region="us-west-2", api_timeout=60, user_metadata={"foo": "bar"}, @@ -334,9 +285,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.sessions.with_raw_response.create( - project_id="projectId", - ) + response = await async_client.sessions.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -345,9 +294,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.sessions.with_streaming_response.create( - project_id="projectId", - ) as response: + async with async_client.sessions.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -398,36 +345,42 @@ async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> Non async def test_method_update(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) + + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncBrowserbase) -> None: + session = await async_client.sessions.update( + id="id", + status="REQUEST_RELEASE", + project_id="projectId", + ) + assert_matches_type(Session, session, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None: response = await async_client.sessions.with_raw_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) @parametrize async def test_streaming_response_update(self, async_client: AsyncBrowserbase) -> None: async with async_client.sessions.with_streaming_response.update( id="id", - project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionUpdateResponse, session, path=["response"]) + assert_matches_type(Session, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -436,7 +389,6 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.sessions.with_raw_response.update( id="", - project_id="projectId", status="REQUEST_RELEASE", ) @@ -478,7 +430,7 @@ async def test_method_debug(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.debug( "id", ) - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -489,7 +441,7 @@ async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) @parametrize async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -500,7 +452,7 @@ async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionDebugResponse, session, path=["response"]) + assert_matches_type(SessionLiveURLs, session, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index 608cc1f..71396d2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -864,7 +864,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.sessions.with_streaming_response.create(project_id="projectId").__enter__() + client.sessions.with_streaming_response.create().__enter__() assert _get_open_connections(client) == 0 @@ -874,7 +874,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.sessions.with_streaming_response.create(project_id="projectId").__enter__() + client.sessions.with_streaming_response.create().__enter__() assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -903,7 +903,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create(project_id="projectId") + response = client.sessions.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -927,9 +927,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -952,9 +950,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} - ) + response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1770,7 +1766,7 @@ async def test_retrying_timeout_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() + await async_client.sessions.with_streaming_response.create().__aenter__() assert _get_open_connections(async_client) == 0 @@ -1782,7 +1778,7 @@ async def test_retrying_status_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() + await async_client.sessions.with_streaming_response.create().__aenter__() assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1811,7 +1807,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create(project_id="projectId") + response = await client.sessions.with_raw_response.create() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1835,9 +1831,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1860,9 +1854,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create( - project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} - ) + response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From b20fc56ad9ef0fceed4d6317511331d2dad6db89 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:05:21 +0000 Subject: [PATCH 37/45] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9a94e54..834ae8a 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/browserbase%2Fbrowserbase-b92143ddb16135de4ff65ce8bcdfe9991d11c73570f42f07ea27e0da86209a44.yml openapi_spec_hash: 16eb6e6c9687f01d2a791775b27dc315 -config_hash: b01d72cbe03bd762a73b05744086b2ec +config_hash: 99e5318dc8a8d75839ec565d94276c71 From f46e475d63812af0f7e53971429adee2dd207d33 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:06:25 +0000 Subject: [PATCH 38/45] feat(api): manual updates --- .stats.yml | 4 +-- .../resources/sessions/sessions.py | 30 +++++++++---------- .../types/session_create_params.py | 12 ++++---- tests/api_resources/test_sessions.py | 4 +-- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/.stats.yml b/.stats.yml index 834ae8a..2d76bb2 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/browserbase%2Fbrowserbase-b92143ddb16135de4ff65ce8bcdfe9991d11c73570f42f07ea27e0da86209a44.yml -openapi_spec_hash: 16eb6e6c9687f01d2a791775b27dc315 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-af59f86ea3ee682d54560eaf6055df5bebacee560749f5f3cbd9c8ff8b5fa8e5.yml +openapi_spec_hash: 72dfdba2efc46c7cbbaa98bd234b72de config_hash: 99e5318dc8a8d75839ec565d94276c71 diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 09fd15a..49ab9ce 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -99,13 +99,13 @@ def with_streaming_response(self) -> SessionsResourceWithStreamingResponse: def create( self, *, + api_timeout: int | Omit = omit, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, - api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -114,12 +114,14 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionCreateResponse: - """Create a Session + """ + Create a Session Args: - extension_id: The uploaded Extension ID. + api_timeout: Duration in seconds after which the session will automatically end. Defaults to + the Project's `defaultTimeout`. - See + extension_id: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on @@ -133,9 +135,6 @@ def create( region: The region where the Session should run. - api_timeout: Duration in seconds after which the session will automatically end. Defaults to - the Project's `defaultTimeout`. - user_metadata: Arbitrary user metadata to attach to the session. To learn more about user metadata, see [User Metadata](/features/sessions#user-metadata). @@ -151,13 +150,13 @@ def create( "/v1/sessions", body=maybe_transform( { + "api_timeout": api_timeout, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, "project_id": project_id, "proxies": proxies, "region": region, - "api_timeout": api_timeout, "user_metadata": user_metadata, }, session_create_params.SessionCreateParams, @@ -369,13 +368,13 @@ def with_streaming_response(self) -> AsyncSessionsResourceWithStreamingResponse: async def create( self, *, + api_timeout: int | Omit = omit, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, - api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -384,12 +383,14 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionCreateResponse: - """Create a Session + """ + Create a Session Args: - extension_id: The uploaded Extension ID. + api_timeout: Duration in seconds after which the session will automatically end. Defaults to + the Project's `defaultTimeout`. - See + extension_id: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension). keep_alive: Set to true to keep the session alive even after disconnections. Available on @@ -403,9 +404,6 @@ async def create( region: The region where the Session should run. - api_timeout: Duration in seconds after which the session will automatically end. Defaults to - the Project's `defaultTimeout`. - user_metadata: Arbitrary user metadata to attach to the session. To learn more about user metadata, see [User Metadata](/features/sessions#user-metadata). @@ -421,13 +419,13 @@ async def create( "/v1/sessions", body=await async_maybe_transform( { + "api_timeout": api_timeout, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, "project_id": project_id, "proxies": proxies, "region": region, - "api_timeout": api_timeout, "user_metadata": user_metadata, }, session_create_params.SessionCreateParams, diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 33dd6fd..24d71f0 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -21,6 +21,12 @@ class SessionCreateParams(TypedDict, total=False): + api_timeout: int + """Duration in seconds after which the session will automatically end. + + Defaults to the Project's `defaultTimeout`. + """ + browser_settings: Annotated[BrowserSettings, PropertyInfo(alias="browserSettings")] extension_id: Annotated[str, PropertyInfo(alias="extensionId")] @@ -50,12 +56,6 @@ class SessionCreateParams(TypedDict, total=False): region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] """The region where the Session should run.""" - api_timeout: Annotated[int, PropertyInfo(alias="timeout")] - """Duration in seconds after which the session will automatically end. - - Defaults to the Project's `defaultTimeout`. - """ - user_metadata: Annotated[Dict[str, object], PropertyInfo(alias="userMetadata")] """Arbitrary user metadata to attach to the session. diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 6a21aa8..571daef 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -31,6 +31,7 @@ def test_method_create(self, client: Browserbase) -> None: @parametrize def test_method_create_with_all_params(self, client: Browserbase) -> None: session = client.sessions.create( + api_timeout=60, browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -55,7 +56,6 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: project_id="projectId", proxies=True, region="us-west-2", - api_timeout=60, user_metadata={"foo": "bar"}, ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @@ -254,6 +254,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.create( + api_timeout=60, browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -278,7 +279,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas project_id="projectId", proxies=True, region="us-west-2", - api_timeout=60, user_metadata={"foo": "bar"}, ) assert_matches_type(SessionCreateResponse, session, path=["response"]) From 3bf8100929de96894b568a39a85a1763d46a6cc9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:16:02 +0000 Subject: [PATCH 39/45] feat(api): manual updates --- .stats.yml | 4 +- .../resources/sessions/sessions.py | 6 +- .../types/session_create_params.py | 83 +------------------ 3 files changed, 9 insertions(+), 84 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2d76bb2..05a7ecd 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/browserbase%2Fbrowserbase-af59f86ea3ee682d54560eaf6055df5bebacee560749f5f3cbd9c8ff8b5fa8e5.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-a9cd420dd3e871aaaad18fb9def41d277ae000607f369657c965a30e779c221a.yml openapi_spec_hash: 72dfdba2efc46c7cbbaa98bd234b72de -config_hash: 99e5318dc8a8d75839ec565d94276c71 +config_hash: b32e9f369edd843015c9b9d4fbc3b4f9 diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 49ab9ce..e7523a8 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Union, Iterable +from typing import Dict, Union from typing_extensions import Literal import httpx @@ -104,7 +104,7 @@ def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, + proxies: Union[bool, object] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -373,7 +373,7 @@ async def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, + proxies: Union[bool, object] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 24d71f0..dc49544 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -2,22 +2,12 @@ from __future__ import annotations -from typing import Dict, Union, Iterable -from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict +from typing import Dict, Union +from typing_extensions import Literal, Required, Annotated, TypedDict from .._utils import PropertyInfo -__all__ = [ - "SessionCreateParams", - "BrowserSettings", - "BrowserSettingsContext", - "BrowserSettingsViewport", - "ProxiesUnionMember1", - "ProxiesUnionMember1BrowserbaseProxyConfig", - "ProxiesUnionMember1BrowserbaseProxyConfigGeolocation", - "ProxiesUnionMember1ExternalProxyConfig", - "ProxiesUnionMember1NoneProxyConfig", -] +__all__ = ["SessionCreateParams", "BrowserSettings", "BrowserSettingsContext", "BrowserSettingsViewport"] class SessionCreateParams(TypedDict, total=False): @@ -47,7 +37,7 @@ class SessionCreateParams(TypedDict, total=False): Can be found in [Settings](https://www.browserbase.com/settings). """ - proxies: Union[bool, Iterable[ProxiesUnionMember1]] + proxies: Union[bool, object] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -121,68 +111,3 @@ class BrowserSettings(TypedDict, total=False): """Enable or disable captcha solving in the browser. Defaults to `true`.""" viewport: BrowserSettingsViewport - - -class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): - """Configuration for geolocation""" - - country: Required[str] - """Country code in ISO 3166-1 alpha-2 format""" - - city: str - """Name of the city. Use spaces for multi-word city names. Optional.""" - - state: str - """US state code (2 characters). Must also specify US as the country. Optional.""" - - -class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): - type: Required[Literal["browserbase"]] - """Type of proxy. - - Always use 'browserbase' for the Browserbase managed proxy network. - """ - - domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] - """Domain pattern for which this proxy should be used. - - If omitted, defaults to all domains. Optional. - """ - - geolocation: ProxiesUnionMember1BrowserbaseProxyConfigGeolocation - """Configuration for geolocation""" - - -class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): - server: Required[str] - """Server URL for external proxy. Required.""" - - type: Required[Literal["external"]] - """Type of proxy. Always 'external' for this config.""" - - domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] - """Domain pattern for which this proxy should be used. - - If omitted, defaults to all domains. Optional. - """ - - password: str - """Password for external proxy authentication. Optional.""" - - username: str - """Username for external proxy authentication. Optional.""" - - -class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): - domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] - """Domain pattern for which site should have proxies disabled.""" - - type: Required[Literal["none"]] - """Type of proxy. Use 'none' to disable proxy for matching domains.""" - - -ProxiesUnionMember1: TypeAlias = Union[ - ProxiesUnionMember1BrowserbaseProxyConfig, - ProxiesUnionMember1ExternalProxyConfig, - ProxiesUnionMember1NoneProxyConfig, -] From 32e4d5194c9b2da94b7d0da9ce16de2208b47a2d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:17:22 +0000 Subject: [PATCH 40/45] feat(api): manual updates --- .stats.yml | 4 +- .../resources/sessions/sessions.py | 6 +- .../types/session_create_params.py | 83 ++++++++++++++++++- 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index 05a7ecd..2d76bb2 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/browserbase%2Fbrowserbase-a9cd420dd3e871aaaad18fb9def41d277ae000607f369657c965a30e779c221a.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-af59f86ea3ee682d54560eaf6055df5bebacee560749f5f3cbd9c8ff8b5fa8e5.yml openapi_spec_hash: 72dfdba2efc46c7cbbaa98bd234b72de -config_hash: b32e9f369edd843015c9b9d4fbc3b4f9 +config_hash: 99e5318dc8a8d75839ec565d94276c71 diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index e7523a8..49ab9ce 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Union +from typing import Dict, Union, Iterable from typing_extensions import Literal import httpx @@ -104,7 +104,7 @@ def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[bool, object] | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -373,7 +373,7 @@ async def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[bool, object] | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index dc49544..24d71f0 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -2,12 +2,22 @@ from __future__ import annotations -from typing import Dict, Union -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing import Dict, Union, Iterable +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from .._utils import PropertyInfo -__all__ = ["SessionCreateParams", "BrowserSettings", "BrowserSettingsContext", "BrowserSettingsViewport"] +__all__ = [ + "SessionCreateParams", + "BrowserSettings", + "BrowserSettingsContext", + "BrowserSettingsViewport", + "ProxiesUnionMember1", + "ProxiesUnionMember1BrowserbaseProxyConfig", + "ProxiesUnionMember1BrowserbaseProxyConfigGeolocation", + "ProxiesUnionMember1ExternalProxyConfig", + "ProxiesUnionMember1NoneProxyConfig", +] class SessionCreateParams(TypedDict, total=False): @@ -37,7 +47,7 @@ class SessionCreateParams(TypedDict, total=False): Can be found in [Settings](https://www.browserbase.com/settings). """ - proxies: Union[bool, object] + proxies: Union[bool, Iterable[ProxiesUnionMember1]] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -111,3 +121,68 @@ class BrowserSettings(TypedDict, total=False): """Enable or disable captcha solving in the browser. Defaults to `true`.""" viewport: BrowserSettingsViewport + + +class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): + """Configuration for geolocation""" + + country: Required[str] + """Country code in ISO 3166-1 alpha-2 format""" + + city: str + """Name of the city. Use spaces for multi-word city names. Optional.""" + + state: str + """US state code (2 characters). Must also specify US as the country. Optional.""" + + +class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): + type: Required[Literal["browserbase"]] + """Type of proxy. + + Always use 'browserbase' for the Browserbase managed proxy network. + """ + + domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] + """Domain pattern for which this proxy should be used. + + If omitted, defaults to all domains. Optional. + """ + + geolocation: ProxiesUnionMember1BrowserbaseProxyConfigGeolocation + """Configuration for geolocation""" + + +class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): + server: Required[str] + """Server URL for external proxy. Required.""" + + type: Required[Literal["external"]] + """Type of proxy. Always 'external' for this config.""" + + domain_pattern: Annotated[str, PropertyInfo(alias="domainPattern")] + """Domain pattern for which this proxy should be used. + + If omitted, defaults to all domains. Optional. + """ + + password: str + """Password for external proxy authentication. Optional.""" + + username: str + """Username for external proxy authentication. Optional.""" + + +class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): + domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] + """Domain pattern for which site should have proxies disabled.""" + + type: Required[Literal["none"]] + """Type of proxy. Use 'none' to disable proxy for matching domains.""" + + +ProxiesUnionMember1: TypeAlias = Union[ + ProxiesUnionMember1BrowserbaseProxyConfig, + ProxiesUnionMember1ExternalProxyConfig, + ProxiesUnionMember1NoneProxyConfig, +] From 892fe7135c9c454526e7bcf76b4c830b57aa6c22 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:18:17 +0000 Subject: [PATCH 41/45] feat(api): manual updates --- .stats.yml | 4 +-- .../resources/sessions/sessions.py | 4 +-- .../types/session_create_params.py | 30 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2d76bb2..c517458 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/browserbase%2Fbrowserbase-af59f86ea3ee682d54560eaf6055df5bebacee560749f5f3cbd9c8ff8b5fa8e5.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-cd8c38feb61fd4aac23600d0e9e51f11db92b1a119d0e807e61835c05a7bf68e.yml openapi_spec_hash: 72dfdba2efc46c7cbbaa98bd234b72de -config_hash: 99e5318dc8a8d75839ec565d94276c71 +config_hash: 2ac55ea7ba4af84e5b7486df92730bcf diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 49ab9ce..21e369b 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -104,7 +104,7 @@ def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionArrayVariant1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -373,7 +373,7 @@ async def create( extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionMember1]] | Omit = omit, + proxies: Union[bool, Iterable[session_create_params.ProxiesUnionArrayVariant1]] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index 24d71f0..d45b315 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -12,11 +12,11 @@ "BrowserSettings", "BrowserSettingsContext", "BrowserSettingsViewport", - "ProxiesUnionMember1", - "ProxiesUnionMember1BrowserbaseProxyConfig", - "ProxiesUnionMember1BrowserbaseProxyConfigGeolocation", - "ProxiesUnionMember1ExternalProxyConfig", - "ProxiesUnionMember1NoneProxyConfig", + "ProxiesUnionArrayVariant1", + "ProxiesUnionArrayVariant1BrowserbaseProxyConfig", + "ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation", + "ProxiesUnionArrayVariant1ExternalProxyConfig", + "ProxiesUnionArrayVariant1NoneProxyConfig", ] @@ -47,7 +47,7 @@ class SessionCreateParams(TypedDict, total=False): Can be found in [Settings](https://www.browserbase.com/settings). """ - proxies: Union[bool, Iterable[ProxiesUnionMember1]] + proxies: Union[bool, Iterable[ProxiesUnionArrayVariant1]] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -123,7 +123,7 @@ class BrowserSettings(TypedDict, total=False): viewport: BrowserSettingsViewport -class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): +class ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): """Configuration for geolocation""" country: Required[str] @@ -136,7 +136,7 @@ class ProxiesUnionMember1BrowserbaseProxyConfigGeolocation(TypedDict, total=Fals """US state code (2 characters). Must also specify US as the country. Optional.""" -class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): +class ProxiesUnionArrayVariant1BrowserbaseProxyConfig(TypedDict, total=False): type: Required[Literal["browserbase"]] """Type of proxy. @@ -149,11 +149,11 @@ class ProxiesUnionMember1BrowserbaseProxyConfig(TypedDict, total=False): If omitted, defaults to all domains. Optional. """ - geolocation: ProxiesUnionMember1BrowserbaseProxyConfigGeolocation + geolocation: ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation """Configuration for geolocation""" -class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): +class ProxiesUnionArrayVariant1ExternalProxyConfig(TypedDict, total=False): server: Required[str] """Server URL for external proxy. Required.""" @@ -173,7 +173,7 @@ class ProxiesUnionMember1ExternalProxyConfig(TypedDict, total=False): """Username for external proxy authentication. Optional.""" -class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): +class ProxiesUnionArrayVariant1NoneProxyConfig(TypedDict, total=False): domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] """Domain pattern for which site should have proxies disabled.""" @@ -181,8 +181,8 @@ class ProxiesUnionMember1NoneProxyConfig(TypedDict, total=False): """Type of proxy. Use 'none' to disable proxy for matching domains.""" -ProxiesUnionMember1: TypeAlias = Union[ - ProxiesUnionMember1BrowserbaseProxyConfig, - ProxiesUnionMember1ExternalProxyConfig, - ProxiesUnionMember1NoneProxyConfig, +ProxiesUnionArrayVariant1: TypeAlias = Union[ + ProxiesUnionArrayVariant1BrowserbaseProxyConfig, + ProxiesUnionArrayVariant1ExternalProxyConfig, + ProxiesUnionArrayVariant1NoneProxyConfig, ] From a0edac2143ff73bc099bf5455191970d46b63628 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:23:02 +0000 Subject: [PATCH 42/45] feat(api): api update --- .stats.yml | 8 +- README.md | 1 + api.md | 29 ++-- src/browserbase/resources/contexts.py | 104 ++------------ src/browserbase/resources/extensions.py | 27 ++-- src/browserbase/resources/projects.py | 32 ++--- .../resources/sessions/sessions.py | 104 +++++++------- src/browserbase/types/__init__.py | 13 +- .../types/context_create_params.py | 4 +- ...ontext.py => context_retrieve_response.py} | 4 +- ...ension.py => extension_create_response.py} | 4 +- .../types/extension_retrieve_response.py | 22 +++ .../types/project_list_response.py | 27 +++- ...roject.py => project_retrieve_response.py} | 4 +- ...ect_usage.py => project_usage_response.py} | 4 +- .../types/session_create_params.py | 98 ++++++++----- ...live_urls.py => session_debug_response.py} | 4 +- .../types/session_list_response.py | 58 +++++++- .../types/session_update_params.py | 12 +- ...{session.py => session_update_response.py} | 4 +- src/browserbase/types/sessions/__init__.py | 2 - .../types/sessions/log_list_response.py | 48 ++++++- .../sessions/recording_retrieve_response.py | 26 +++- src/browserbase/types/sessions/session_log.py | 46 ------ .../types/sessions/session_recording.py | 24 ---- tests/api_resources/test_contexts.py | 120 +++------------- tests/api_resources/test_extensions.py | 26 ++-- tests/api_resources/test_projects.py | 26 ++-- tests/api_resources/test_sessions.py | 136 ++++++++++++------ tests/test_client.py | 28 ++-- 30 files changed, 529 insertions(+), 516 deletions(-) rename src/browserbase/types/{context.py => context_retrieve_response.py} (84%) rename src/browserbase/types/{extension.py => extension_create_response.py} (85%) create mode 100644 src/browserbase/types/extension_retrieve_response.py rename src/browserbase/types/{project.py => project_retrieve_response.py} (87%) rename src/browserbase/types/{project_usage.py => project_usage_response.py} (78%) rename src/browserbase/types/{session_live_urls.py => session_debug_response.py} (88%) rename src/browserbase/types/{session.py => session_update_response.py} (95%) delete mode 100644 src/browserbase/types/sessions/session_log.py delete mode 100644 src/browserbase/types/sessions/session_recording.py diff --git a/.stats.yml b/.stats.yml index c517458..b000c8c 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/browserbase%2Fbrowserbase-cd8c38feb61fd4aac23600d0e9e51f11db92b1a119d0e807e61835c05a7bf68e.yml -openapi_spec_hash: 72dfdba2efc46c7cbbaa98bd234b72de -config_hash: 2ac55ea7ba4af84e5b7486df92730bcf +configured_endpoints: 18 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-be7a4aeebb1605262935b4b3ab446a95b1fad8a7d18098943dd548c8a486ef13.yml +openapi_spec_hash: 1c950a109f80140711e7ae2cf87fddad +config_hash: b3ca4ec5b02e5333af51ebc2e9fdef1b diff --git a/README.md b/README.md index b379eb7..6863a76 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ from browserbase import Browserbase client = Browserbase() session = client.sessions.create( + project_id="projectId", browser_settings={}, ) print(session.browser_settings) diff --git a/api.md b/api.md index d2d26e5..0145485 100644 --- a/api.md +++ b/api.md @@ -3,28 +3,27 @@ Types: ```python -from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse +from browserbase.types import ContextCreateResponse, ContextRetrieveResponse, ContextUpdateResponse ``` Methods: - client.contexts.create(\*\*params) -> ContextCreateResponse -- client.contexts.retrieve(id) -> Context +- client.contexts.retrieve(id) -> ContextRetrieveResponse - client.contexts.update(id) -> ContextUpdateResponse -- client.contexts.delete(id) -> None # Extensions Types: ```python -from browserbase.types import Extension +from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse ``` Methods: -- client.extensions.create(\*\*params) -> Extension -- client.extensions.retrieve(id) -> Extension +- client.extensions.create(\*\*params) -> ExtensionCreateResponse +- client.extensions.retrieve(id) -> ExtensionRetrieveResponse - client.extensions.delete(id) -> None # Projects @@ -32,14 +31,14 @@ Methods: Types: ```python -from browserbase.types import Project, ProjectUsage, ProjectListResponse +from browserbase.types import ProjectRetrieveResponse, ProjectListResponse, ProjectUsageResponse ``` Methods: -- client.projects.retrieve(id) -> Project +- client.projects.retrieve(id) -> ProjectRetrieveResponse - client.projects.list() -> ProjectListResponse -- client.projects.usage(id) -> ProjectUsage +- client.projects.usage(id) -> ProjectUsageResponse # Sessions @@ -47,11 +46,11 @@ Types: ```python from browserbase.types import ( - Session, - SessionLiveURLs, SessionCreateResponse, SessionRetrieveResponse, + SessionUpdateResponse, SessionListResponse, + SessionDebugResponse, ) ``` @@ -59,9 +58,9 @@ Methods: - client.sessions.create(\*\*params) -> SessionCreateResponse - client.sessions.retrieve(id) -> SessionRetrieveResponse -- client.sessions.update(id, \*\*params) -> Session +- client.sessions.update(id, \*\*params) -> SessionUpdateResponse - client.sessions.list(\*\*params) -> SessionListResponse -- client.sessions.debug(id) -> SessionLiveURLs +- client.sessions.debug(id) -> SessionDebugResponse ## Downloads @@ -74,7 +73,7 @@ Methods: Types: ```python -from browserbase.types.sessions import SessionLog, LogListResponse +from browserbase.types.sessions import LogListResponse ``` Methods: @@ -86,7 +85,7 @@ Methods: Types: ```python -from browserbase.types.sessions import SessionRecording, RecordingRetrieveResponse +from browserbase.types.sessions import RecordingRetrieveResponse ``` Methods: diff --git a/src/browserbase/resources/contexts.py b/src/browserbase/resources/contexts.py index 03af85f..d2bb416 100644 --- a/src/browserbase/resources/contexts.py +++ b/src/browserbase/resources/contexts.py @@ -5,7 +5,7 @@ import httpx from ..types import context_create_params -from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given +from .._types import Body, Query, Headers, NotGiven, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -16,9 +16,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.context import Context from ..types.context_create_response import ContextCreateResponse from ..types.context_update_response import ContextUpdateResponse +from ..types.context_retrieve_response import ContextRetrieveResponse __all__ = ["ContextsResource", "AsyncContextsResource"] @@ -46,7 +46,7 @@ def with_streaming_response(self) -> ContextsResourceWithStreamingResponse: def create( self, *, - project_id: str | Omit = omit, + project_id: str, # 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, @@ -89,9 +89,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Context: + ) -> ContextRetrieveResponse: """ - Context + Get a Context Args: extra_headers: Send extra headers @@ -109,7 +109,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Context, + cast_to=ContextRetrieveResponse, ) def update( @@ -124,7 +124,7 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update Context + Update a Context Args: extra_headers: Send extra headers @@ -145,40 +145,6 @@ def update( cast_to=ContextUpdateResponse, ) - def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Delete Context - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( - f"/v1/contexts/{id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - class AsyncContextsResource(AsyncAPIResource): @cached_property @@ -203,7 +169,7 @@ def with_streaming_response(self) -> AsyncContextsResourceWithStreamingResponse: async def create( self, *, - project_id: str | Omit = omit, + project_id: str, # 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, @@ -246,9 +212,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Context: + ) -> ContextRetrieveResponse: """ - Context + Get a Context Args: extra_headers: Send extra headers @@ -266,7 +232,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Context, + cast_to=ContextRetrieveResponse, ) async def update( @@ -281,7 +247,7 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ContextUpdateResponse: """ - Update Context + Update a Context Args: extra_headers: Send extra headers @@ -302,40 +268,6 @@ async def update( cast_to=ContextUpdateResponse, ) - async def delete( - self, - id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Delete Context - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._delete( - f"/v1/contexts/{id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, - ) - class ContextsResourceWithRawResponse: def __init__(self, contexts: ContextsResource) -> None: @@ -350,9 +282,6 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_raw_response_wrapper( contexts.update, ) - self.delete = to_raw_response_wrapper( - contexts.delete, - ) class AsyncContextsResourceWithRawResponse: @@ -368,9 +297,6 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_raw_response_wrapper( contexts.update, ) - self.delete = async_to_raw_response_wrapper( - contexts.delete, - ) class ContextsResourceWithStreamingResponse: @@ -386,9 +312,6 @@ def __init__(self, contexts: ContextsResource) -> None: self.update = to_streamed_response_wrapper( contexts.update, ) - self.delete = to_streamed_response_wrapper( - contexts.delete, - ) class AsyncContextsResourceWithStreamingResponse: @@ -404,6 +327,3 @@ def __init__(self, contexts: AsyncContextsResource) -> None: self.update = async_to_streamed_response_wrapper( contexts.update, ) - self.delete = async_to_streamed_response_wrapper( - contexts.delete, - ) diff --git a/src/browserbase/resources/extensions.py b/src/browserbase/resources/extensions.py index 882a495..21d06e7 100644 --- a/src/browserbase/resources/extensions.py +++ b/src/browserbase/resources/extensions.py @@ -18,7 +18,8 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.extension import Extension +from ..types.extension_create_response import ExtensionCreateResponse +from ..types.extension_retrieve_response import ExtensionRetrieveResponse __all__ = ["ExtensionsResource", "AsyncExtensionsResource"] @@ -53,7 +54,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionCreateResponse: """ Upload an Extension @@ -79,7 +80,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionCreateResponse, ) def retrieve( @@ -92,9 +93,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionRetrieveResponse: """ - Extension + Get an Extension Args: extra_headers: Send extra headers @@ -112,7 +113,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionRetrieveResponse, ) def delete( @@ -127,7 +128,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete Extension + Delete an Extension Args: extra_headers: Send extra headers @@ -180,7 +181,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionCreateResponse: """ Upload an Extension @@ -206,7 +207,7 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionCreateResponse, ) async def retrieve( @@ -219,9 +220,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Extension: + ) -> ExtensionRetrieveResponse: """ - Extension + Get an Extension Args: extra_headers: Send extra headers @@ -239,7 +240,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Extension, + cast_to=ExtensionRetrieveResponse, ) async def delete( @@ -254,7 +255,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete Extension + Delete an Extension Args: extra_headers: Send extra headers diff --git a/src/browserbase/resources/projects.py b/src/browserbase/resources/projects.py index a6ae633..62c28af 100644 --- a/src/browserbase/resources/projects.py +++ b/src/browserbase/resources/projects.py @@ -14,9 +14,9 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.project import Project -from ..types.project_usage import ProjectUsage from ..types.project_list_response import ProjectListResponse +from ..types.project_usage_response import ProjectUsageResponse +from ..types.project_retrieve_response import ProjectRetrieveResponse __all__ = ["ProjectsResource", "AsyncProjectsResource"] @@ -51,9 +51,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Project: + ) -> ProjectRetrieveResponse: """ - Project + Get a Project Args: extra_headers: Send extra headers @@ -71,7 +71,7 @@ def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Project, + cast_to=ProjectRetrieveResponse, ) def list( @@ -84,7 +84,7 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List projects""" + """List Projects""" return self._get( "/v1/projects", options=make_request_options( @@ -103,9 +103,9 @@ def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsage: + ) -> ProjectUsageResponse: """ - Project Usage + Get Project Usage Args: extra_headers: Send extra headers @@ -123,7 +123,7 @@ def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsage, + cast_to=ProjectUsageResponse, ) @@ -157,9 +157,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Project: + ) -> ProjectRetrieveResponse: """ - Project + Get a Project Args: extra_headers: Send extra headers @@ -177,7 +177,7 @@ async def retrieve( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Project, + cast_to=ProjectRetrieveResponse, ) async def list( @@ -190,7 +190,7 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ProjectListResponse: - """List projects""" + """List Projects""" return await self._get( "/v1/projects", options=make_request_options( @@ -209,9 +209,9 @@ async def usage( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ProjectUsage: + ) -> ProjectUsageResponse: """ - Project Usage + Get Project Usage Args: extra_headers: Send extra headers @@ -229,7 +229,7 @@ async def usage( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ProjectUsage, + cast_to=ProjectUsageResponse, ) diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py index 21e369b..ceaaeb8 100644 --- a/src/browserbase/resources/sessions/sessions.py +++ b/src/browserbase/resources/sessions/sessions.py @@ -51,10 +51,10 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.session import Session -from ...types.session_live_urls import SessionLiveURLs from ...types.session_list_response import SessionListResponse +from ...types.session_debug_response import SessionDebugResponse from ...types.session_create_response import SessionCreateResponse +from ...types.session_update_response import SessionUpdateResponse from ...types.session_retrieve_response import SessionRetrieveResponse __all__ = ["SessionsResource", "AsyncSessionsResource"] @@ -99,13 +99,13 @@ def with_streaming_response(self) -> SessionsResourceWithStreamingResponse: def create( self, *, - api_timeout: int | Omit = omit, + project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, - project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionArrayVariant1]] | Omit = omit, + proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, + api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -114,12 +114,13 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionCreateResponse: - """ - Create a Session + """Create a Session Args: - api_timeout: Duration in seconds after which the session will automatically end. Defaults to - the Project's `defaultTimeout`. + project_id: The Project ID. + + Can be found in + [Settings](https://www.browserbase.com/settings). extension_id: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension). @@ -127,14 +128,14 @@ def create( keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. - project_id: The Project ID. Can be found in - [Settings](https://www.browserbase.com/settings). - proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. region: The region where the Session should run. + api_timeout: Duration in seconds after which the session will automatically end. Defaults to + the Project's `defaultTimeout`. + user_metadata: Arbitrary user metadata to attach to the session. To learn more about user metadata, see [User Metadata](/features/sessions#user-metadata). @@ -150,13 +151,13 @@ def create( "/v1/sessions", body=maybe_transform( { - "api_timeout": api_timeout, + "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, - "project_id": project_id, "proxies": proxies, "region": region, + "api_timeout": api_timeout, "user_metadata": user_metadata, }, session_create_params.SessionCreateParams, @@ -179,7 +180,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Session + Get a Session Args: extra_headers: Send extra headers @@ -204,25 +205,26 @@ def update( self, id: str, *, + project_id: str, status: Literal["REQUEST_RELEASE"], - project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Session: - """ - Update Session + ) -> SessionUpdateResponse: + """Update a Session Args: - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before - session's timeout to avoid additional charges. + project_id: The Project ID. - project_id: The Project ID. Can be found in + Can be found in [Settings](https://www.browserbase.com/settings). + status: Set to `REQUEST_RELEASE` to request that the session complete. Use before + session's timeout to avoid additional charges. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -237,15 +239,15 @@ def update( f"/v1/sessions/{id}", body=maybe_transform( { - "status": status, "project_id": project_id, + "status": status, }, session_update_params.SessionUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Session, + cast_to=SessionUpdateResponse, ) def list( @@ -305,7 +307,7 @@ def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionLiveURLs: + ) -> SessionDebugResponse: """ Session Live URLs @@ -325,7 +327,7 @@ def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionLiveURLs, + cast_to=SessionDebugResponse, ) @@ -368,13 +370,13 @@ def with_streaming_response(self) -> AsyncSessionsResourceWithStreamingResponse: async def create( self, *, - api_timeout: int | Omit = omit, + project_id: str, browser_settings: session_create_params.BrowserSettings | Omit = omit, extension_id: str | Omit = omit, keep_alive: bool | Omit = omit, - project_id: str | Omit = omit, - proxies: Union[bool, Iterable[session_create_params.ProxiesUnionArrayVariant1]] | Omit = omit, + proxies: Union[Iterable[session_create_params.ProxiesUnionMember0], bool] | Omit = omit, region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] | Omit = omit, + api_timeout: int | Omit = omit, user_metadata: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -383,12 +385,13 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionCreateResponse: - """ - Create a Session + """Create a Session Args: - api_timeout: Duration in seconds after which the session will automatically end. Defaults to - the Project's `defaultTimeout`. + project_id: The Project ID. + + Can be found in + [Settings](https://www.browserbase.com/settings). extension_id: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension). @@ -396,14 +399,14 @@ async def create( keep_alive: Set to true to keep the session alive even after disconnections. Available on the Hobby Plan and above. - project_id: The Project ID. Can be found in - [Settings](https://www.browserbase.com/settings). - proxies: Proxy configuration. Can be true for default proxy, or an array of proxy configurations. region: The region where the Session should run. + api_timeout: Duration in seconds after which the session will automatically end. Defaults to + the Project's `defaultTimeout`. + user_metadata: Arbitrary user metadata to attach to the session. To learn more about user metadata, see [User Metadata](/features/sessions#user-metadata). @@ -419,13 +422,13 @@ async def create( "/v1/sessions", body=await async_maybe_transform( { - "api_timeout": api_timeout, + "project_id": project_id, "browser_settings": browser_settings, "extension_id": extension_id, "keep_alive": keep_alive, - "project_id": project_id, "proxies": proxies, "region": region, + "api_timeout": api_timeout, "user_metadata": user_metadata, }, session_create_params.SessionCreateParams, @@ -448,7 +451,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SessionRetrieveResponse: """ - Session + Get a Session Args: extra_headers: Send extra headers @@ -473,25 +476,26 @@ async def update( self, id: str, *, + project_id: str, status: Literal["REQUEST_RELEASE"], - project_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> Session: - """ - Update Session + ) -> SessionUpdateResponse: + """Update a Session Args: - status: Set to `REQUEST_RELEASE` to request that the session complete. Use before - session's timeout to avoid additional charges. + project_id: The Project ID. - project_id: The Project ID. Can be found in + Can be found in [Settings](https://www.browserbase.com/settings). + status: Set to `REQUEST_RELEASE` to request that the session complete. Use before + session's timeout to avoid additional charges. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -506,15 +510,15 @@ async def update( f"/v1/sessions/{id}", body=await async_maybe_transform( { - "status": status, "project_id": project_id, + "status": status, }, session_update_params.SessionUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Session, + cast_to=SessionUpdateResponse, ) async def list( @@ -574,7 +578,7 @@ async def debug( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SessionLiveURLs: + ) -> SessionDebugResponse: """ Session Live URLs @@ -594,7 +598,7 @@ async def debug( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SessionLiveURLs, + cast_to=SessionDebugResponse, ) diff --git a/src/browserbase/types/__init__.py b/src/browserbase/types/__init__.py index 4dd85dd..20e2f90 100644 --- a/src/browserbase/types/__init__.py +++ b/src/browserbase/types/__init__.py @@ -2,20 +2,21 @@ from __future__ import annotations -from .context import Context as Context -from .project import Project as Project -from .session import Session as Session -from .extension import Extension as Extension -from .project_usage import ProjectUsage as ProjectUsage -from .session_live_urls import SessionLiveURLs as SessionLiveURLs from .session_list_params import SessionListParams as SessionListParams from .context_create_params import ContextCreateParams as ContextCreateParams from .project_list_response import ProjectListResponse as ProjectListResponse from .session_create_params import SessionCreateParams as SessionCreateParams from .session_list_response import SessionListResponse as SessionListResponse from .session_update_params import SessionUpdateParams as SessionUpdateParams +from .project_usage_response import ProjectUsageResponse as ProjectUsageResponse +from .session_debug_response import SessionDebugResponse as SessionDebugResponse from .context_create_response import ContextCreateResponse as ContextCreateResponse from .context_update_response import ContextUpdateResponse as ContextUpdateResponse from .extension_create_params import ExtensionCreateParams as ExtensionCreateParams from .session_create_response import SessionCreateResponse as SessionCreateResponse +from .session_update_response import SessionUpdateResponse as SessionUpdateResponse +from .context_retrieve_response import ContextRetrieveResponse as ContextRetrieveResponse +from .extension_create_response import ExtensionCreateResponse as ExtensionCreateResponse +from .project_retrieve_response import ProjectRetrieveResponse as ProjectRetrieveResponse from .session_retrieve_response import SessionRetrieveResponse as SessionRetrieveResponse +from .extension_retrieve_response import ExtensionRetrieveResponse as ExtensionRetrieveResponse diff --git a/src/browserbase/types/context_create_params.py b/src/browserbase/types/context_create_params.py index 66c6c46..75cd1fc 100644 --- a/src/browserbase/types/context_create_params.py +++ b/src/browserbase/types/context_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Annotated, TypedDict +from typing_extensions import Required, Annotated, TypedDict from .._utils import PropertyInfo @@ -10,7 +10,7 @@ class ContextCreateParams(TypedDict, total=False): - project_id: Annotated[str, PropertyInfo(alias="projectId")] + project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] """The Project ID. Can be found in [Settings](https://www.browserbase.com/settings). diff --git a/src/browserbase/types/context.py b/src/browserbase/types/context_retrieve_response.py similarity index 84% rename from src/browserbase/types/context.py rename to src/browserbase/types/context_retrieve_response.py index cb5c32f..c2cd692 100644 --- a/src/browserbase/types/context.py +++ b/src/browserbase/types/context_retrieve_response.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["Context"] +__all__ = ["ContextRetrieveResponse"] -class Context(BaseModel): +class ContextRetrieveResponse(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension.py b/src/browserbase/types/extension_create_response.py similarity index 85% rename from src/browserbase/types/extension.py rename to src/browserbase/types/extension_create_response.py index 94582c3..d2b74f4 100644 --- a/src/browserbase/types/extension.py +++ b/src/browserbase/types/extension_create_response.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["Extension"] +__all__ = ["ExtensionCreateResponse"] -class Extension(BaseModel): +class ExtensionCreateResponse(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/extension_retrieve_response.py b/src/browserbase/types/extension_retrieve_response.py new file mode 100644 index 0000000..c786348 --- /dev/null +++ b/src/browserbase/types/extension_retrieve_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["ExtensionRetrieveResponse"] + + +class ExtensionRetrieveResponse(BaseModel): + id: str + + created_at: datetime = FieldInfo(alias="createdAt") + + file_name: str = FieldInfo(alias="fileName") + + project_id: str = FieldInfo(alias="projectId") + """The Project ID linked to the uploaded Extension.""" + + updated_at: datetime = FieldInfo(alias="updatedAt") diff --git a/src/browserbase/types/project_list_response.py b/src/browserbase/types/project_list_response.py index 2d05a23..e364b52 100644 --- a/src/browserbase/types/project_list_response.py +++ b/src/browserbase/types/project_list_response.py @@ -1,10 +1,31 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List +from datetime import datetime from typing_extensions import TypeAlias -from .project import Project +from pydantic import Field as FieldInfo -__all__ = ["ProjectListResponse"] +from .._models import BaseModel -ProjectListResponse: TypeAlias = List[Project] +__all__ = ["ProjectListResponse", "ProjectListResponseItem"] + + +class ProjectListResponseItem(BaseModel): + id: str + + concurrency: int + """The maximum number of sessions that this project can run concurrently.""" + + created_at: datetime = FieldInfo(alias="createdAt") + + default_timeout: int = FieldInfo(alias="defaultTimeout") + + name: str + + owner_id: str = FieldInfo(alias="ownerId") + + updated_at: datetime = FieldInfo(alias="updatedAt") + + +ProjectListResponse: TypeAlias = List[ProjectListResponseItem] diff --git a/src/browserbase/types/project.py b/src/browserbase/types/project_retrieve_response.py similarity index 87% rename from src/browserbase/types/project.py rename to src/browserbase/types/project_retrieve_response.py index dc3cf33..7812667 100644 --- a/src/browserbase/types/project.py +++ b/src/browserbase/types/project_retrieve_response.py @@ -6,10 +6,10 @@ from .._models import BaseModel -__all__ = ["Project"] +__all__ = ["ProjectRetrieveResponse"] -class Project(BaseModel): +class ProjectRetrieveResponse(BaseModel): id: str concurrency: int diff --git a/src/browserbase/types/project_usage.py b/src/browserbase/types/project_usage_response.py similarity index 78% rename from src/browserbase/types/project_usage.py rename to src/browserbase/types/project_usage_response.py index c8a03f5..b52fccf 100644 --- a/src/browserbase/types/project_usage.py +++ b/src/browserbase/types/project_usage_response.py @@ -4,10 +4,10 @@ from .._models import BaseModel -__all__ = ["ProjectUsage"] +__all__ = ["ProjectUsageResponse"] -class ProjectUsage(BaseModel): +class ProjectUsageResponse(BaseModel): browser_minutes: int = FieldInfo(alias="browserMinutes") proxy_bytes: int = FieldInfo(alias="proxyBytes") diff --git a/src/browserbase/types/session_create_params.py b/src/browserbase/types/session_create_params.py index d45b315..2ba3640 100644 --- a/src/browserbase/types/session_create_params.py +++ b/src/browserbase/types/session_create_params.py @@ -2,29 +2,31 @@ from __future__ import annotations -from typing import Dict, Union, Iterable +from typing import Dict, List, Union, Iterable from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict +from .._types import SequenceNotStr from .._utils import PropertyInfo __all__ = [ "SessionCreateParams", "BrowserSettings", "BrowserSettingsContext", + "BrowserSettingsFingerprint", + "BrowserSettingsFingerprintScreen", "BrowserSettingsViewport", - "ProxiesUnionArrayVariant1", - "ProxiesUnionArrayVariant1BrowserbaseProxyConfig", - "ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation", - "ProxiesUnionArrayVariant1ExternalProxyConfig", - "ProxiesUnionArrayVariant1NoneProxyConfig", + "ProxiesUnionMember0", + "ProxiesUnionMember0UnionMember0", + "ProxiesUnionMember0UnionMember0Geolocation", + "ProxiesUnionMember0UnionMember1", ] class SessionCreateParams(TypedDict, total=False): - api_timeout: int - """Duration in seconds after which the session will automatically end. + project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] + """The Project ID. - Defaults to the Project's `defaultTimeout`. + Can be found in [Settings](https://www.browserbase.com/settings). """ browser_settings: Annotated[BrowserSettings, PropertyInfo(alias="browserSettings")] @@ -41,13 +43,7 @@ class SessionCreateParams(TypedDict, total=False): Available on the Hobby Plan and above. """ - project_id: Annotated[str, PropertyInfo(alias="projectId")] - """The Project ID. - - Can be found in [Settings](https://www.browserbase.com/settings). - """ - - proxies: Union[bool, Iterable[ProxiesUnionArrayVariant1]] + proxies: Union[Iterable[ProxiesUnionMember0], bool] """Proxy configuration. Can be true for default proxy, or an array of proxy configurations. @@ -56,6 +52,12 @@ class SessionCreateParams(TypedDict, total=False): region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] """The region where the Session should run.""" + api_timeout: Annotated[int, PropertyInfo(alias="timeout")] + """Duration in seconds after which the session will automatically end. + + Defaults to the Project's `defaultTimeout`. + """ + user_metadata: Annotated[Dict[str, object], PropertyInfo(alias="userMetadata")] """Arbitrary user metadata to attach to the session. @@ -72,10 +74,42 @@ class BrowserSettingsContext(TypedDict, total=False): """Whether or not to persist the context after browsing. Defaults to `false`.""" +class BrowserSettingsFingerprintScreen(TypedDict, total=False): + max_height: Annotated[int, PropertyInfo(alias="maxHeight")] + + max_width: Annotated[int, PropertyInfo(alias="maxWidth")] + + min_height: Annotated[int, PropertyInfo(alias="minHeight")] + + min_width: Annotated[int, PropertyInfo(alias="minWidth")] + + +class BrowserSettingsFingerprint(TypedDict, total=False): + """ + See usage examples [on the Stealth Mode page](/features/stealth-mode#fingerprinting) + """ + + browsers: List[Literal["chrome", "edge", "firefox", "safari"]] + + devices: List[Literal["desktop", "mobile"]] + + http_version: Annotated[Literal["1", "2"], PropertyInfo(alias="httpVersion")] + + locales: SequenceNotStr[str] + + operating_systems: Annotated[ + List[Literal["android", "ios", "linux", "macos", "windows"]], PropertyInfo(alias="operatingSystems") + ] + + screen: BrowserSettingsFingerprintScreen + + class BrowserSettingsViewport(TypedDict, total=False): height: int + """The height of the browser.""" width: int + """The width of the browser.""" class BrowserSettings(TypedDict, total=False): @@ -105,6 +139,12 @@ class BrowserSettings(TypedDict, total=False): See [Upload Extension](/reference/api/upload-an-extension). """ + fingerprint: BrowserSettingsFingerprint + """ + See usage examples + [on the Stealth Mode page](/features/stealth-mode#fingerprinting) + """ + log_session: Annotated[bool, PropertyInfo(alias="logSession")] """Enable or disable session logging. Defaults to `true`.""" @@ -123,8 +163,8 @@ class BrowserSettings(TypedDict, total=False): viewport: BrowserSettingsViewport -class ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation(TypedDict, total=False): - """Configuration for geolocation""" +class ProxiesUnionMember0UnionMember0Geolocation(TypedDict, total=False): + """Geographic location for the proxy. Optional.""" country: Required[str] """Country code in ISO 3166-1 alpha-2 format""" @@ -136,7 +176,7 @@ class ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation(TypedDict, tota """US state code (2 characters). Must also specify US as the country. Optional.""" -class ProxiesUnionArrayVariant1BrowserbaseProxyConfig(TypedDict, total=False): +class ProxiesUnionMember0UnionMember0(TypedDict, total=False): type: Required[Literal["browserbase"]] """Type of proxy. @@ -149,11 +189,11 @@ class ProxiesUnionArrayVariant1BrowserbaseProxyConfig(TypedDict, total=False): If omitted, defaults to all domains. Optional. """ - geolocation: ProxiesUnionArrayVariant1BrowserbaseProxyConfigGeolocation - """Configuration for geolocation""" + geolocation: ProxiesUnionMember0UnionMember0Geolocation + """Geographic location for the proxy. Optional.""" -class ProxiesUnionArrayVariant1ExternalProxyConfig(TypedDict, total=False): +class ProxiesUnionMember0UnionMember1(TypedDict, total=False): server: Required[str] """Server URL for external proxy. Required.""" @@ -173,16 +213,4 @@ class ProxiesUnionArrayVariant1ExternalProxyConfig(TypedDict, total=False): """Username for external proxy authentication. Optional.""" -class ProxiesUnionArrayVariant1NoneProxyConfig(TypedDict, total=False): - domain_pattern: Required[Annotated[str, PropertyInfo(alias="domainPattern")]] - """Domain pattern for which site should have proxies disabled.""" - - type: Required[Literal["none"]] - """Type of proxy. Use 'none' to disable proxy for matching domains.""" - - -ProxiesUnionArrayVariant1: TypeAlias = Union[ - ProxiesUnionArrayVariant1BrowserbaseProxyConfig, - ProxiesUnionArrayVariant1ExternalProxyConfig, - ProxiesUnionArrayVariant1NoneProxyConfig, -] +ProxiesUnionMember0: TypeAlias = Union[ProxiesUnionMember0UnionMember0, ProxiesUnionMember0UnionMember1] diff --git a/src/browserbase/types/session_live_urls.py b/src/browserbase/types/session_debug_response.py similarity index 88% rename from src/browserbase/types/session_live_urls.py rename to src/browserbase/types/session_debug_response.py index 3c7ba32..9cee7a7 100644 --- a/src/browserbase/types/session_live_urls.py +++ b/src/browserbase/types/session_debug_response.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["SessionLiveURLs", "Page"] +__all__ = ["SessionDebugResponse", "Page"] class Page(BaseModel): @@ -23,7 +23,7 @@ class Page(BaseModel): url: str -class SessionLiveURLs(BaseModel): +class SessionDebugResponse(BaseModel): debugger_fullscreen_url: str = FieldInfo(alias="debuggerFullscreenUrl") debugger_url: str = FieldInfo(alias="debuggerUrl") diff --git a/src/browserbase/types/session_list_response.py b/src/browserbase/types/session_list_response.py index ca162dd..4c1bd88 100644 --- a/src/browserbase/types/session_list_response.py +++ b/src/browserbase/types/session_list_response.py @@ -1,10 +1,58 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List -from typing_extensions import TypeAlias +from typing import Dict, List, Optional +from datetime import datetime +from typing_extensions import Literal, TypeAlias -from .session import Session +from pydantic import Field as FieldInfo -__all__ = ["SessionListResponse"] +from .._models import BaseModel -SessionListResponse: TypeAlias = List[Session] +__all__ = ["SessionListResponse", "SessionListResponseItem"] + + +class SessionListResponseItem(BaseModel): + id: str + + created_at: datetime = FieldInfo(alias="createdAt") + + expires_at: datetime = FieldInfo(alias="expiresAt") + + keep_alive: bool = FieldInfo(alias="keepAlive") + """Indicates if the Session was created to be kept alive upon disconnections""" + + project_id: str = FieldInfo(alias="projectId") + """The Project ID linked to the Session.""" + + proxy_bytes: int = FieldInfo(alias="proxyBytes") + """Bytes used via the [Proxy](/features/stealth-mode#proxies-and-residential-ips)""" + + region: Literal["us-west-2", "us-east-1", "eu-central-1", "ap-southeast-1"] + """The region where the Session is running.""" + + started_at: datetime = FieldInfo(alias="startedAt") + + status: Literal["RUNNING", "ERROR", "TIMED_OUT", "COMPLETED"] + + updated_at: datetime = FieldInfo(alias="updatedAt") + + avg_cpu_usage: Optional[int] = FieldInfo(alias="avgCpuUsage", default=None) + """CPU used by the Session""" + + context_id: Optional[str] = FieldInfo(alias="contextId", default=None) + """Optional. The Context linked to the Session.""" + + ended_at: Optional[datetime] = FieldInfo(alias="endedAt", default=None) + + memory_usage: Optional[int] = FieldInfo(alias="memoryUsage", default=None) + """Memory used by the Session""" + + user_metadata: Optional[Dict[str, object]] = FieldInfo(alias="userMetadata", default=None) + """Arbitrary user metadata to attach to the session. + + To learn more about user metadata, see + [User Metadata](/features/sessions#user-metadata). + """ + + +SessionListResponse: TypeAlias = List[SessionListResponseItem] diff --git a/src/browserbase/types/session_update_params.py b/src/browserbase/types/session_update_params.py index 71c589d..66dcd35 100644 --- a/src/browserbase/types/session_update_params.py +++ b/src/browserbase/types/session_update_params.py @@ -10,14 +10,14 @@ class SessionUpdateParams(TypedDict, total=False): - status: Required[Literal["REQUEST_RELEASE"]] - """Set to `REQUEST_RELEASE` to request that the session complete. + project_id: Required[Annotated[str, PropertyInfo(alias="projectId")]] + """The Project ID. - Use before session's timeout to avoid additional charges. + Can be found in [Settings](https://www.browserbase.com/settings). """ - project_id: Annotated[str, PropertyInfo(alias="projectId")] - """The Project ID. + status: Required[Literal["REQUEST_RELEASE"]] + """Set to `REQUEST_RELEASE` to request that the session complete. - Can be found in [Settings](https://www.browserbase.com/settings). + Use before session's timeout to avoid additional charges. """ diff --git a/src/browserbase/types/session.py b/src/browserbase/types/session_update_response.py similarity index 95% rename from src/browserbase/types/session.py rename to src/browserbase/types/session_update_response.py index 16450e2..67a1371 100644 --- a/src/browserbase/types/session.py +++ b/src/browserbase/types/session_update_response.py @@ -8,10 +8,10 @@ from .._models import BaseModel -__all__ = ["Session"] +__all__ = ["SessionUpdateResponse"] -class Session(BaseModel): +class SessionUpdateResponse(BaseModel): id: str created_at: datetime = FieldInfo(alias="createdAt") diff --git a/src/browserbase/types/sessions/__init__.py b/src/browserbase/types/sessions/__init__.py index 0cef6b1..69d5470 100644 --- a/src/browserbase/types/sessions/__init__.py +++ b/src/browserbase/types/sessions/__init__.py @@ -2,9 +2,7 @@ from __future__ import annotations -from .session_log import SessionLog as SessionLog from .log_list_response import LogListResponse as LogListResponse -from .session_recording import SessionRecording as SessionRecording from .upload_create_params import UploadCreateParams as UploadCreateParams from .upload_create_response import UploadCreateResponse as UploadCreateResponse from .recording_retrieve_response import RecordingRetrieveResponse as RecordingRetrieveResponse diff --git a/src/browserbase/types/sessions/log_list_response.py b/src/browserbase/types/sessions/log_list_response.py index 2b325a8..efd848a 100644 --- a/src/browserbase/types/sessions/log_list_response.py +++ b/src/browserbase/types/sessions/log_list_response.py @@ -1,10 +1,50 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import Dict, List, Optional from typing_extensions import TypeAlias -from .session_log import SessionLog +from pydantic import Field as FieldInfo -__all__ = ["LogListResponse"] +from ..._models import BaseModel -LogListResponse: TypeAlias = List[SessionLog] +__all__ = ["LogListResponse", "LogListResponseItem", "LogListResponseItemRequest", "LogListResponseItemResponse"] + + +class LogListResponseItemRequest(BaseModel): + params: Dict[str, object] + + raw_body: str = FieldInfo(alias="rawBody") + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class LogListResponseItemResponse(BaseModel): + raw_body: str = FieldInfo(alias="rawBody") + + result: Dict[str, object] + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +class LogListResponseItem(BaseModel): + method: str + + page_id: int = FieldInfo(alias="pageId") + + session_id: str = FieldInfo(alias="sessionId") + + frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) + + loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) + + request: Optional[LogListResponseItemRequest] = None + + response: Optional[LogListResponseItemResponse] = None + + timestamp: Optional[int] = None + """milliseconds that have elapsed since the UNIX epoch""" + + +LogListResponse: TypeAlias = List[LogListResponseItem] diff --git a/src/browserbase/types/sessions/recording_retrieve_response.py b/src/browserbase/types/sessions/recording_retrieve_response.py index 951969b..d3613b8 100644 --- a/src/browserbase/types/sessions/recording_retrieve_response.py +++ b/src/browserbase/types/sessions/recording_retrieve_response.py @@ -1,10 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import Dict, List from typing_extensions import TypeAlias -from .session_recording import SessionRecording +from pydantic import Field as FieldInfo -__all__ = ["RecordingRetrieveResponse"] +from ..._models import BaseModel -RecordingRetrieveResponse: TypeAlias = List[SessionRecording] +__all__ = ["RecordingRetrieveResponse", "RecordingRetrieveResponseItem"] + + +class RecordingRetrieveResponseItem(BaseModel): + data: Dict[str, object] + """ + See + [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). + """ + + session_id: str = FieldInfo(alias="sessionId") + + timestamp: int + """milliseconds that have elapsed since the UNIX epoch""" + + type: int + + +RecordingRetrieveResponse: TypeAlias = List[RecordingRetrieveResponseItem] diff --git a/src/browserbase/types/sessions/session_log.py b/src/browserbase/types/sessions/session_log.py deleted file mode 100644 index 428f518..0000000 --- a/src/browserbase/types/sessions/session_log.py +++ /dev/null @@ -1,46 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, Optional - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["SessionLog", "Request", "Response"] - - -class Request(BaseModel): - params: Dict[str, object] - - raw_body: str = FieldInfo(alias="rawBody") - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class Response(BaseModel): - raw_body: str = FieldInfo(alias="rawBody") - - result: Dict[str, object] - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" - - -class SessionLog(BaseModel): - method: str - - page_id: int = FieldInfo(alias="pageId") - - session_id: str = FieldInfo(alias="sessionId") - - frame_id: Optional[str] = FieldInfo(alias="frameId", default=None) - - loader_id: Optional[str] = FieldInfo(alias="loaderId", default=None) - - request: Optional[Request] = None - - response: Optional[Response] = None - - timestamp: Optional[int] = None - """milliseconds that have elapsed since the UNIX epoch""" diff --git a/src/browserbase/types/sessions/session_recording.py b/src/browserbase/types/sessions/session_recording.py deleted file mode 100644 index c847137..0000000 --- a/src/browserbase/types/sessions/session_recording.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = ["SessionRecording"] - - -class SessionRecording(BaseModel): - data: Dict[str, object] - """ - See - [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). - """ - - session_id: str = FieldInfo(alias="sessionId") - - timestamp: int - """milliseconds that have elapsed since the UNIX epoch""" - - type: int diff --git a/tests/api_resources/test_contexts.py b/tests/api_resources/test_contexts.py index 31fb97d..4ad2773 100644 --- a/tests/api_resources/test_contexts.py +++ b/tests/api_resources/test_contexts.py @@ -9,7 +9,11 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import Context, ContextCreateResponse, ContextUpdateResponse +from browserbase.types import ( + ContextCreateResponse, + ContextUpdateResponse, + ContextRetrieveResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -19,11 +23,6 @@ class TestContexts: @parametrize def test_method_create(self, client: Browserbase) -> None: - context = client.contexts.create() - assert_matches_type(ContextCreateResponse, context, path=["response"]) - - @parametrize - def test_method_create_with_all_params(self, client: Browserbase) -> None: context = client.contexts.create( project_id="projectId", ) @@ -31,7 +30,9 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.create() + response = client.contexts.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -40,7 +41,9 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.create() as response: + with client.contexts.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -54,7 +57,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: context = client.contexts.retrieve( "id", ) - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -65,7 +68,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -76,7 +79,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -125,44 +128,6 @@ def test_path_params_update(self, client: Browserbase) -> None: "", ) - @parametrize - def test_method_delete(self, client: Browserbase) -> None: - context = client.contexts.delete( - "id", - ) - assert context is None - - @parametrize - def test_raw_response_delete(self, client: Browserbase) -> None: - response = client.contexts.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - context = response.parse() - assert context is None - - @parametrize - def test_streaming_response_delete(self, client: Browserbase) -> None: - with client.contexts.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - context = response.parse() - assert context is None - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_delete(self, client: Browserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.contexts.with_raw_response.delete( - "", - ) - class TestAsyncContexts: parametrize = pytest.mark.parametrize( @@ -171,11 +136,6 @@ class TestAsyncContexts: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: - context = await async_client.contexts.create() - assert_matches_type(ContextCreateResponse, context, path=["response"]) - - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.create( project_id="projectId", ) @@ -183,7 +143,9 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.create() + response = await async_client.contexts.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -192,7 +154,9 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.create() as response: + async with async_client.contexts.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -206,7 +170,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: context = await async_client.contexts.retrieve( "id", ) - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -217,7 +181,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -228,7 +192,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" context = await response.parse() - assert_matches_type(Context, context, path=["response"]) + assert_matches_type(ContextRetrieveResponse, context, path=["response"]) assert cast(Any, response.is_closed) is True @@ -276,41 +240,3 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: await async_client.contexts.with_raw_response.update( "", ) - - @parametrize - async def test_method_delete(self, async_client: AsyncBrowserbase) -> None: - context = await async_client.contexts.delete( - "id", - ) - assert context is None - - @parametrize - async def test_raw_response_delete(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.contexts.with_raw_response.delete( - "id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - context = await response.parse() - assert context is None - - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncBrowserbase) -> None: - async with async_client.contexts.with_streaming_response.delete( - "id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - context = await response.parse() - assert context is None - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_delete(self, async_client: AsyncBrowserbase) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.contexts.with_raw_response.delete( - "", - ) diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py index 6b6a018..e32ae9b 100644 --- a/tests/api_resources/test_extensions.py +++ b/tests/api_resources/test_extensions.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import Extension +from browserbase.types import ExtensionCreateResponse, ExtensionRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_create(self, client: Browserbase) -> None: extension = client.extensions.create( file=b"raw file contents", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize def test_raw_response_create(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_create(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_create(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -53,7 +53,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: extension = client.extensions.retrieve( "id", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -64,7 +64,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -75,7 +75,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -135,7 +135,7 @@ async def test_method_create(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.create( file=b"raw file contents", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -146,7 +146,7 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: @@ -157,7 +157,7 @@ async def test_streaming_response_create(self, async_client: AsyncBrowserbase) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True @@ -166,7 +166,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: extension = await async_client.extensions.retrieve( "id", ) - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -177,7 +177,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -188,7 +188,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" extension = await response.parse() - assert_matches_type(Extension, extension, path=["response"]) + assert_matches_type(ExtensionRetrieveResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index c8241bf..0d8e3c9 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -9,7 +9,7 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type -from browserbase.types import Project, ProjectUsage, ProjectListResponse +from browserbase.types import ProjectListResponse, ProjectUsageResponse, ProjectRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,7 +22,7 @@ def test_method_retrieve(self, client: Browserbase) -> None: project = client.projects.retrieve( "id", ) - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize def test_raw_response_retrieve(self, client: Browserbase) -> None: @@ -33,7 +33,7 @@ def test_raw_response_retrieve(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize def test_streaming_response_retrieve(self, client: Browserbase) -> None: @@ -44,7 +44,7 @@ def test_streaming_response_retrieve(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -85,7 +85,7 @@ def test_method_usage(self, client: Browserbase) -> None: project = client.projects.usage( "id", ) - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize def test_raw_response_usage(self, client: Browserbase) -> None: @@ -96,7 +96,7 @@ def test_raw_response_usage(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize def test_streaming_response_usage(self, client: Browserbase) -> None: @@ -107,7 +107,7 @@ def test_streaming_response_usage(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -129,7 +129,7 @@ async def test_method_retrieve(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.retrieve( "id", ) - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -140,7 +140,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncBrowserbase) -> No assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) -> None: @@ -151,7 +151,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncBrowserbase) assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(Project, project, path=["response"]) + assert_matches_type(ProjectRetrieveResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True @@ -192,7 +192,7 @@ async def test_method_usage(self, async_client: AsyncBrowserbase) -> None: project = await async_client.projects.usage( "id", ) - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -203,7 +203,7 @@ async def test_raw_response_usage(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) @parametrize async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> None: @@ -214,7 +214,7 @@ async def test_streaming_response_usage(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" project = await response.parse() - assert_matches_type(ProjectUsage, project, path=["response"]) + assert_matches_type(ProjectUsageResponse, project, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_sessions.py b/tests/api_resources/test_sessions.py index 571daef..7a16f64 100644 --- a/tests/api_resources/test_sessions.py +++ b/tests/api_resources/test_sessions.py @@ -10,10 +10,10 @@ from browserbase import Browserbase, AsyncBrowserbase from tests.utils import assert_matches_type from browserbase.types import ( - Session, - SessionLiveURLs, SessionListResponse, + SessionDebugResponse, SessionCreateResponse, + SessionUpdateResponse, SessionRetrieveResponse, ) @@ -25,13 +25,15 @@ class TestSessions: @parametrize def test_method_create(self, client: Browserbase) -> None: - session = client.sessions.create() + session = client.sessions.create( + project_id="projectId", + ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Browserbase) -> None: session = client.sessions.create( - api_timeout=60, + project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -42,6 +44,19 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: "persist": True, }, "extension_id": "extensionId", + "fingerprint": { + "browsers": ["chrome"], + "devices": ["desktop"], + "http_version": "1", + "locales": ["string"], + "operating_systems": ["android"], + "screen": { + "max_height": 0, + "max_width": 0, + "min_height": 0, + "min_width": 0, + }, + }, "log_session": True, "os": "windows", "record_session": True, @@ -53,16 +68,28 @@ def test_method_create_with_all_params(self, client: Browserbase) -> None: }, extension_id="extensionId", keep_alive=True, - project_id="projectId", - proxies=True, + proxies=[ + { + "type": "browserbase", + "domain_pattern": "domainPattern", + "geolocation": { + "country": "xx", + "city": "city", + "state": "xx", + }, + } + ], region="us-west-2", + api_timeout=60, user_metadata={"foo": "bar"}, ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize def test_raw_response_create(self, client: Browserbase) -> None: - response = client.sessions.with_raw_response.create() + response = client.sessions.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -71,7 +98,9 @@ def test_raw_response_create(self, client: Browserbase) -> None: @parametrize def test_streaming_response_create(self, client: Browserbase) -> None: - with client.sessions.with_streaming_response.create() as response: + with client.sessions.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -122,42 +151,36 @@ def test_path_params_retrieve(self, client: Browserbase) -> None: def test_method_update(self, client: Browserbase) -> None: session = client.sessions.update( id="id", - status="REQUEST_RELEASE", - ) - assert_matches_type(Session, session, path=["response"]) - - @parametrize - def test_method_update_with_all_params(self, client: Browserbase) -> None: - session = client.sessions.update( - id="id", - status="REQUEST_RELEASE", project_id="projectId", + status="REQUEST_RELEASE", ) - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize def test_raw_response_update(self, client: Browserbase) -> None: response = client.sessions.with_raw_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize def test_streaming_response_update(self, client: Browserbase) -> None: with client.sessions.with_streaming_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -166,6 +189,7 @@ def test_path_params_update(self, client: Browserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.sessions.with_raw_response.update( id="", + project_id="projectId", status="REQUEST_RELEASE", ) @@ -207,7 +231,7 @@ def test_method_debug(self, client: Browserbase) -> None: session = client.sessions.debug( "id", ) - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize def test_raw_response_debug(self, client: Browserbase) -> None: @@ -218,7 +242,7 @@ def test_raw_response_debug(self, client: Browserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize def test_streaming_response_debug(self, client: Browserbase) -> None: @@ -229,7 +253,7 @@ def test_streaming_response_debug(self, client: Browserbase) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -248,13 +272,15 @@ class TestAsyncSessions: @parametrize async def test_method_create(self, async_client: AsyncBrowserbase) -> None: - session = await async_client.sessions.create() + session = await async_client.sessions.create( + project_id="projectId", + ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.create( - api_timeout=60, + project_id="projectId", browser_settings={ "advanced_stealth": True, "block_ads": True, @@ -265,6 +291,19 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas "persist": True, }, "extension_id": "extensionId", + "fingerprint": { + "browsers": ["chrome"], + "devices": ["desktop"], + "http_version": "1", + "locales": ["string"], + "operating_systems": ["android"], + "screen": { + "max_height": 0, + "max_width": 0, + "min_height": 0, + "min_width": 0, + }, + }, "log_session": True, "os": "windows", "record_session": True, @@ -276,16 +315,28 @@ async def test_method_create_with_all_params(self, async_client: AsyncBrowserbas }, extension_id="extensionId", keep_alive=True, - project_id="projectId", - proxies=True, + proxies=[ + { + "type": "browserbase", + "domain_pattern": "domainPattern", + "geolocation": { + "country": "xx", + "city": "city", + "state": "xx", + }, + } + ], region="us-west-2", + api_timeout=60, user_metadata={"foo": "bar"}, ) assert_matches_type(SessionCreateResponse, session, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None: - response = await async_client.sessions.with_raw_response.create() + response = await async_client.sessions.with_raw_response.create( + project_id="projectId", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -294,7 +345,9 @@ async def test_raw_response_create(self, async_client: AsyncBrowserbase) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncBrowserbase) -> None: - async with async_client.sessions.with_streaming_response.create() as response: + async with async_client.sessions.with_streaming_response.create( + project_id="projectId", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -345,42 +398,36 @@ async def test_path_params_retrieve(self, async_client: AsyncBrowserbase) -> Non async def test_method_update(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.update( id="id", - status="REQUEST_RELEASE", - ) - assert_matches_type(Session, session, path=["response"]) - - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncBrowserbase) -> None: - session = await async_client.sessions.update( - id="id", - status="REQUEST_RELEASE", project_id="projectId", + status="REQUEST_RELEASE", ) - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize async def test_raw_response_update(self, async_client: AsyncBrowserbase) -> None: response = await async_client.sessions.with_raw_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) @parametrize async def test_streaming_response_update(self, async_client: AsyncBrowserbase) -> None: async with async_client.sessions.with_streaming_response.update( id="id", + project_id="projectId", status="REQUEST_RELEASE", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(Session, session, path=["response"]) + assert_matches_type(SessionUpdateResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True @@ -389,6 +436,7 @@ async def test_path_params_update(self, async_client: AsyncBrowserbase) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.sessions.with_raw_response.update( id="", + project_id="projectId", status="REQUEST_RELEASE", ) @@ -430,7 +478,7 @@ async def test_method_debug(self, async_client: AsyncBrowserbase) -> None: session = await async_client.sessions.debug( "id", ) - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -441,7 +489,7 @@ async def test_raw_response_debug(self, async_client: AsyncBrowserbase) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) @parametrize async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> None: @@ -452,7 +500,7 @@ async def test_streaming_response_debug(self, async_client: AsyncBrowserbase) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" session = await response.parse() - assert_matches_type(SessionLiveURLs, session, path=["response"]) + assert_matches_type(SessionDebugResponse, session, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index 71396d2..608cc1f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -864,7 +864,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.sessions.with_streaming_response.create().__enter__() + client.sessions.with_streaming_response.create(project_id="projectId").__enter__() assert _get_open_connections(client) == 0 @@ -874,7 +874,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.sessions.with_streaming_response.create().__enter__() + client.sessions.with_streaming_response.create(project_id="projectId").__enter__() assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -903,7 +903,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create() + response = client.sessions.with_raw_response.create(project_id="projectId") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -927,7 +927,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) + response = client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -950,7 +952,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) + response = client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1766,7 +1770,7 @@ async def test_retrying_timeout_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.sessions.with_streaming_response.create().__aenter__() + await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() assert _get_open_connections(async_client) == 0 @@ -1778,7 +1782,7 @@ async def test_retrying_status_errors_doesnt_leak( respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.sessions.with_streaming_response.create().__aenter__() + await async_client.sessions.with_streaming_response.create(project_id="projectId").__aenter__() assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1807,7 +1811,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create() + response = await client.sessions.with_raw_response.create(project_id="projectId") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1831,7 +1835,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": Omit()}) + response = await client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1854,7 +1860,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.sessions.with_raw_response.create(extra_headers={"x-stainless-retry-count": "42"}) + response = await client.sessions.with_raw_response.create( + project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From 0451d3509b3bd14695e506ecc5ece3367a91eba6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:13:40 +0000 Subject: [PATCH 43/45] chore(internal): add request options to SSE classes --- src/browserbase/_response.py | 3 +++ src/browserbase/_streaming.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browserbase/_response.py b/src/browserbase/_response.py index 5f8d0f4..eeef642 100644 --- a/src/browserbase/_response.py +++ b/src/browserbase/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/browserbase/_streaming.py b/src/browserbase/_streaming.py index d107619..d1ebde6 100644 --- a/src/browserbase/_streaming.py +++ b/src/browserbase/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Browserbase, AsyncBrowserbase + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Browserbase, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncBrowserbase, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() From b768b4f48ad6ed219cb3b7407baf0f598452f044 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:26:26 +0000 Subject: [PATCH 44/45] chore(internal): make `test_proxy_environment_variables` more resilient --- tests/test_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 608cc1f..15eebfc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -961,6 +961,8 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultHttpxClient() @@ -1873,6 +1875,8 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has this set + monkeypatch.delenv("HTTP_PROXY", raising=False) client = DefaultAsyncHttpxClient() From d41e8923622c81b56708358315a9b50dd20c54b4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:26:53 +0000 Subject: [PATCH 45/45] release: 1.5.0-alpha.2 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 58 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/browserbase/_version.py | 2 +- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3d362d5..70fc11c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.5.0-alpha.1" + ".": "1.5.0-alpha.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 76a56a6..829cb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # Changelog +## 1.5.0-alpha.2 (2026-02-24) + +Full Changelog: [v1.5.0-alpha.1...v1.5.0-alpha.2](https://github.com/browserbase/sdk-python/compare/v1.5.0-alpha.1...v1.5.0-alpha.2) + +### Features + +* **api:** api update ([a0edac2](https://github.com/browserbase/sdk-python/commit/a0edac2143ff73bc099bf5455191970d46b63628)) +* **api:** api update ([0b8da8a](https://github.com/browserbase/sdk-python/commit/0b8da8acb8aa4a299655719a2b31d472f8d9750f)) +* **api:** api update ([20dcbdc](https://github.com/browserbase/sdk-python/commit/20dcbdc81a95efe53e2e57ff7b205e8a3c023bbc)) +* **api:** api update ([97edfd0](https://github.com/browserbase/sdk-python/commit/97edfd04ce506478373c878b68cab3a8d6adc80d)) +* **api:** manual updates ([892fe71](https://github.com/browserbase/sdk-python/commit/892fe7135c9c454526e7bcf76b4c830b57aa6c22)) +* **api:** manual updates ([32e4d51](https://github.com/browserbase/sdk-python/commit/32e4d5194c9b2da94b7d0da9ce16de2208b47a2d)) +* **api:** manual updates ([3bf8100](https://github.com/browserbase/sdk-python/commit/3bf8100929de96894b568a39a85a1763d46a6cc9)) +* **api:** manual updates ([f46e475](https://github.com/browserbase/sdk-python/commit/f46e475d63812af0f7e53971429adee2dd207d33)) +* **api:** manual updates ([7ace939](https://github.com/browserbase/sdk-python/commit/7ace9396d265afabebd57169e6a32dcedec3b0d4)) +* **api:** manual updates ([94d9db6](https://github.com/browserbase/sdk-python/commit/94d9db6142e868b739649027144eade26e092cb3)) +* **api:** manual updates ([e281b89](https://github.com/browserbase/sdk-python/commit/e281b8958b9a5b30ca90af0db36693a1eeaed8c5)) +* **api:** manual updates ([f0ba58f](https://github.com/browserbase/sdk-python/commit/f0ba58f620f3b30e4f275cce521de1d6e7fd7164)) +* **client:** add custom JSON encoder for extended type support ([da8ce14](https://github.com/browserbase/sdk-python/commit/da8ce14deb91e1da722dbafa3f7e35bfd1fbd983)) +* **client:** add support for binary request streaming ([972c837](https://github.com/browserbase/sdk-python/commit/972c8370ed69360de2479cf0fc818be3df4679cf)) + + +### Bug Fixes + +* **client:** close streams without requiring full consumption ([bb617be](https://github.com/browserbase/sdk-python/commit/bb617bee85cc709ea14c4b53eac06058f28318e9)) +* compat with Python 3.14 ([8f4df7c](https://github.com/browserbase/sdk-python/commit/8f4df7c2a20dd87d54d5ca12a8060a25223d2ef5)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([135104e](https://github.com/browserbase/sdk-python/commit/135104eec5555bd650d25743dd91587e9c74e549)) +* ensure streams are always closed ([588f8f4](https://github.com/browserbase/sdk-python/commit/588f8f480efaa914d5bbcb693dadd97936d66750)) +* **pydantic:** ignore model extras in pydantic v2 ([#156](https://github.com/browserbase/sdk-python/issues/156)) ([9cae18c](https://github.com/browserbase/sdk-python/commit/9cae18c71078c8baae46bfb13298a8eadcfe5675)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([6e13710](https://github.com/browserbase/sdk-python/commit/6e1371033109ed3a2519660bcc0da3f44f65f097)) +* use async_to_httpx_files in patch method ([4370dab](https://github.com/browserbase/sdk-python/commit/4370dab0f116d5535259ee557c0dddf3fc006c80)) + + +### Chores + +* add missing docstrings ([0332edf](https://github.com/browserbase/sdk-python/commit/0332edf2964f32119e074d86baf3ef35e30f1b8d)) +* add Python 3.14 classifier and testing ([ec88bf3](https://github.com/browserbase/sdk-python/commit/ec88bf3f829ced4db3ee9bbaa249d0f447d0095d)) +* bump `httpx-aiohttp` version to 0.1.9 ([42685a1](https://github.com/browserbase/sdk-python/commit/42685a189cf7a465d8696fbc8902123567f1e9e0)) +* **ci:** upgrade `actions/github-script` ([9363c3d](https://github.com/browserbase/sdk-python/commit/9363c3dd4142917eca9e7f5872d68d73b0400a17)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([b0d4efb](https://github.com/browserbase/sdk-python/commit/b0d4efb5945050ddfa449e468306e39aadeccf6a)) +* **docs:** use environment variables for authentication in code snippets ([97a777f](https://github.com/browserbase/sdk-python/commit/97a777f837a1f06940e05506e4b0237ace54cef8)) +* format all `api.md` files ([36b3fc5](https://github.com/browserbase/sdk-python/commit/36b3fc50c44221848b583e5d5f66e06e9f857a14)) +* **internal/tests:** avoid race condition with implicit client cleanup ([6ca21dc](https://github.com/browserbase/sdk-python/commit/6ca21dcf117d076b9baaa43e0b1efd676c518845)) +* **internal:** add `--fix` argument to lint script ([b96137c](https://github.com/browserbase/sdk-python/commit/b96137ced74f55b77e904026ab76b8bf306a7543)) +* **internal:** add missing files argument to base client ([851f268](https://github.com/browserbase/sdk-python/commit/851f268b6fbfa815f819bd3390d732ee26445b09)) +* **internal:** add request options to SSE classes ([0451d35](https://github.com/browserbase/sdk-python/commit/0451d3509b3bd14695e506ecc5ece3367a91eba6)) +* **internal:** bump dependencies ([b0c3306](https://github.com/browserbase/sdk-python/commit/b0c33068ffe4c033748a4ca7e4394cdd7c4c97be)) +* **internal:** codegen related update ([5dad097](https://github.com/browserbase/sdk-python/commit/5dad0978043a3fe8d65c5ff06a064e2d9b40cda9)) +* **internal:** detect missing future annotations with ruff ([ea60157](https://github.com/browserbase/sdk-python/commit/ea60157e52c3f8477ecf20f2a39ec2a722c83fed)) +* **internal:** fix lint error on Python 3.14 ([ba06a3e](https://github.com/browserbase/sdk-python/commit/ba06a3e6d617c4b10be4874856006797fdec6e93)) +* **internal:** grammar fix (it's -> its) ([1d6aebd](https://github.com/browserbase/sdk-python/commit/1d6aebda8211f3ffe1602420cfca5672d84561bd)) +* **internal:** make `test_proxy_environment_variables` more resilient ([b768b4f](https://github.com/browserbase/sdk-python/commit/b768b4f48ad6ed219cb3b7407baf0f598452f044)) +* **internal:** update `actions/checkout` version ([7b01d95](https://github.com/browserbase/sdk-python/commit/7b01d951f1e4abaa345ee686d8b08c9066f183ca)) +* **package:** drop Python 3.8 support ([e36cf2b](https://github.com/browserbase/sdk-python/commit/e36cf2bcad59f24078b30d5e463e0e2325f9439c)) +* speedup initial import ([ca27085](https://github.com/browserbase/sdk-python/commit/ca270852b7f1c3dd5544f73daeec8ddb41eac253)) +* update lockfile ([738e9be](https://github.com/browserbase/sdk-python/commit/738e9be4acd99ad82e69ea876f1249948310f896)) +* update mock server docs ([9c92875](https://github.com/browserbase/sdk-python/commit/9c928759fb88085da94df9921ef35663d1da926e)) + ## 1.5.0-alpha.1 (2025-10-07) Full Changelog: [v1.5.0-alpha.0...v1.5.0-alpha.1](https://github.com/browserbase/sdk-python/compare/v1.5.0-alpha.0...v1.5.0-alpha.1) diff --git a/pyproject.toml b/pyproject.toml index 7e6634c..4d5c491 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "browserbase" -version = "1.5.0-alpha.1" +version = "1.5.0-alpha.2" description = "The official Python library for the Browserbase API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/browserbase/_version.py b/src/browserbase/_version.py index 6fa8f70..afe412c 100644 --- a/src/browserbase/_version.py +++ b/src/browserbase/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "browserbase" -__version__ = "1.5.0-alpha.1" # x-release-please-version +__version__ = "1.5.0-alpha.2" # x-release-please-version