From 013f2b1e79b65211382c076d93911c18f11cbf26 Mon Sep 17 00:00:00 2001 From: stainless-bot Date: Mon, 28 Oct 2024 23:14:08 +0000 Subject: [PATCH] feat(api): update via SDK Studio --- README.md | 47 ++++++++++---------- SECURITY.md | 2 +- pyproject.toml | 4 +- src/browserbase/__init__.py | 2 - src/browserbase/_client.py | 87 ++++++------------------------------- tests/test_client.py | 68 ++++++++++------------------- 6 files changed, 66 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index a170345..53e20a9 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,13 @@ from browserbase import Browserbase client = Browserbase( # This is the default and can be omitted api_key=os.environ.get("BROWSERBASE_API_KEY"), - # or 'production' | 'local'; defaults to "production". - environment="development", ) -context = client.contexts.create( - project_id="projectId", +session = client.sessions.create( + project_id="your_project_id", + proxies=True, ) -print(context.id) +print(session.id) ``` While you can provide an `api_key` keyword argument, @@ -60,16 +59,15 @@ from browserbase import AsyncBrowserbase client = AsyncBrowserbase( # This is the default and can be omitted api_key=os.environ.get("BROWSERBASE_API_KEY"), - # or 'production' | 'local'; defaults to "production". - environment="development", ) async def main() -> None: - context = await client.contexts.create( - project_id="projectId", + session = await client.sessions.create( + project_id="your_project_id", + proxies=True, ) - print(context.id) + print(session.id) asyncio.run(main()) @@ -102,8 +100,9 @@ from browserbase import Browserbase client = Browserbase() try: - client.contexts.create( - project_id="projectId", + client.sessions.create( + project_id="your_project_id", + proxies=True, ) except browserbase.APIConnectionError as e: print("The server could not be reached") @@ -147,8 +146,9 @@ client = Browserbase( ) # Or, configure per-request: -client.with_options(max_retries=5).contexts.create( - project_id="projectId", +client.with_options(max_retries=5).sessions.create( + project_id="your_project_id", + proxies=True, ) ``` @@ -172,8 +172,9 @@ client = Browserbase( ) # Override per-request: -client.with_options(timeout=5.0).contexts.create( - project_id="projectId", +client.with_options(timeout=5.0).sessions.create( + project_id="your_project_id", + proxies=True, ) ``` @@ -213,13 +214,14 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from browserbase import Browserbase client = Browserbase() -response = client.contexts.with_raw_response.create( - project_id="projectId", +response = client.sessions.with_raw_response.create( + project_id="your_project_id", + proxies=True, ) print(response.headers.get('X-My-Header')) -context = response.parse() # get the object that `contexts.create()` would have returned -print(context.id) +session = response.parse() # get the object that `sessions.create()` would have returned +print(session.id) ``` These methods return an [`APIResponse`](https://github.com/browserbase/sdk-python/tree/main/src/browserbase/_response.py) object. @@ -233,8 +235,9 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.contexts.with_streaming_response.create( - project_id="projectId", +with client.sessions.with_streaming_response.create( + project_id="your_project_id", + proxies=True, ) as response: print(response.headers.get("X-My-Header")) diff --git a/SECURITY.md b/SECURITY.md index 06c5b6e..4fdede8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,7 +20,7 @@ or products provided by Browserbase please follow the respective company's secur ### Browserbase Terms and Policies -Please contact dev-feedback@browserbase.com for any questions or concerns regarding security of our services. +Please contact support@browserbase.com for any questions or concerns regarding security of our services. --- diff --git a/pyproject.toml b/pyproject.toml index cf85fb2..efbaac8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "browserbase" version = "0.1.0-alpha.6" -description = "The official Python library for the browserbase API" +description = "The official Python library for the Browserbase API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Browserbase", email = "dev-feedback@browserbase.com" }, +{ name = "Browserbase", email = "support@browserbase.com" }, ] dependencies = [ "httpx>=0.23.0, <1", diff --git a/src/browserbase/__init__.py b/src/browserbase/__init__.py index ea5a598..4b1d280 100644 --- a/src/browserbase/__init__.py +++ b/src/browserbase/__init__.py @@ -4,7 +4,6 @@ from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path from ._client import ( - ENVIRONMENTS, Client, Stream, Timeout, @@ -69,7 +68,6 @@ "AsyncStream", "Browserbase", "AsyncBrowserbase", - "ENVIRONMENTS", "file_from_path", "BaseModel", "DEFAULT_TIMEOUT", diff --git a/src/browserbase/_client.py b/src/browserbase/_client.py index 461f9da..b3ed32f 100644 --- a/src/browserbase/_client.py +++ b/src/browserbase/_client.py @@ -3,8 +3,8 @@ from __future__ import annotations import os -from typing import Any, Dict, Union, Mapping, cast -from typing_extensions import Self, Literal, override +from typing import Any, Union, Mapping +from typing_extensions import Self, override import httpx @@ -33,7 +33,6 @@ ) __all__ = [ - "ENVIRONMENTS", "Timeout", "Transport", "ProxiesTypes", @@ -45,12 +44,6 @@ "AsyncClient", ] -ENVIRONMENTS: Dict[str, str] = { - "production": "https://api.browserbase.com", - "development": "https://api.dev.browserbase.com", - "local": "http://api.localhost", -} - class Browserbase(SyncAPIClient): contexts: resources.ContextsResource @@ -63,14 +56,11 @@ class Browserbase(SyncAPIClient): # client options api_key: str - _environment: Literal["production", "development", "local"] | NotGiven - def __init__( self, *, api_key: str | None = None, - environment: Literal["production", "development", "local"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, + base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -89,7 +79,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous browserbase client instance. + """Construct a new synchronous Browserbase client instance. This automatically infers the `api_key` argument from the `BROWSERBASE_API_KEY` environment variable if it is not provided. """ @@ -101,31 +91,10 @@ def __init__( ) self.api_key = api_key - self._environment = environment - - base_url_env = os.environ.get("BROWSERBASE_BASE_URL") - if is_given(base_url) and base_url is not None: - # cast required because mypy doesn't understand the type narrowing - base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] - elif is_given(environment): - if base_url_env and base_url is not None: - raise ValueError( - "Ambiguous URL; The `BROWSERBASE_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", - ) - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc - elif base_url_env is not None: - base_url = base_url_env - else: - self._environment = environment = "production" - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc + if base_url is None: + base_url = os.environ.get("BROWSERBASE_BASE_URL") + if base_url is None: + base_url = f"https://api.browserbase.com" super().__init__( version=__version__, @@ -169,7 +138,6 @@ def copy( self, *, api_key: str | None = None, - environment: Literal["production", "development", "local"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.Client | None = None, @@ -205,7 +173,6 @@ def copy( return self.__class__( api_key=api_key or self.api_key, base_url=base_url or self.base_url, - environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, @@ -263,14 +230,11 @@ class AsyncBrowserbase(AsyncAPIClient): # client options api_key: str - _environment: Literal["production", "development", "local"] | NotGiven - def __init__( self, *, api_key: str | None = None, - environment: Literal["production", "development", "local"] | NotGiven = NOT_GIVEN, - base_url: str | httpx.URL | None | NotGiven = NOT_GIVEN, + base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -289,7 +253,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async browserbase client instance. + """Construct a new async Browserbase client instance. This automatically infers the `api_key` argument from the `BROWSERBASE_API_KEY` environment variable if it is not provided. """ @@ -301,31 +265,10 @@ def __init__( ) self.api_key = api_key - self._environment = environment - - base_url_env = os.environ.get("BROWSERBASE_BASE_URL") - if is_given(base_url) and base_url is not None: - # cast required because mypy doesn't understand the type narrowing - base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] - elif is_given(environment): - if base_url_env and base_url is not None: - raise ValueError( - "Ambiguous URL; The `BROWSERBASE_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", - ) - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc - elif base_url_env is not None: - base_url = base_url_env - else: - self._environment = environment = "production" - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc + if base_url is None: + base_url = os.environ.get("BROWSERBASE_BASE_URL") + if base_url is None: + base_url = f"https://api.browserbase.com" super().__init__( version=__version__, @@ -369,7 +312,6 @@ def copy( self, *, api_key: str | None = None, - environment: Literal["production", "development", "local"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.AsyncClient | None = None, @@ -405,7 +347,6 @@ def copy( return self.__class__( api_key=api_key or self.api_key, base_url=base_url or self.base_url, - environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, diff --git a/tests/test_client.py b/tests/test_client.py index 9fe3b90..9679e17 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -556,16 +556,6 @@ def test_base_url_env(self) -> None: client = Browserbase(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" - # explicit environment arg requires explicitness - with update_env(BROWSERBASE_BASE_URL="http://localhost:5000/from/env"): - with pytest.raises(ValueError, match=r"you must pass base_url=None"): - Browserbase(api_key=api_key, _strict_response_validation=True, environment="production") - - client = Browserbase( - base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" - ) - assert str(client.base_url).startswith("https://api.browserbase.com") - @pytest.mark.parametrize( "client", [ @@ -728,12 +718,12 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/v1/contexts").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): self.client.post( - "/v1/contexts", - body=cast(object, dict(project_id="projectId")), + "/v1/sessions", + body=cast(object, dict(project_id="your_project_id", proxies=True)), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -743,12 +733,12 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/v1/contexts").mock(return_value=httpx.Response(500)) + respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): self.client.post( - "/v1/contexts", - body=cast(object, dict(project_id="projectId")), + "/v1/sessions", + body=cast(object, dict(project_id="your_project_id", proxies=True)), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -779,9 +769,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/contexts").mock(side_effect=retry_handler) + respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.contexts.with_raw_response.create(project_id="projectId") + 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 @@ -803,9 +793,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/contexts").mock(side_effect=retry_handler) + respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.contexts.with_raw_response.create( + response = client.sessions.with_raw_response.create( project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} ) @@ -828,9 +818,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/contexts").mock(side_effect=retry_handler) + respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = client.contexts.with_raw_response.create( + response = client.sessions.with_raw_response.create( project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} ) @@ -1342,16 +1332,6 @@ def test_base_url_env(self) -> None: client = AsyncBrowserbase(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" - # explicit environment arg requires explicitness - with update_env(BROWSERBASE_BASE_URL="http://localhost:5000/from/env"): - with pytest.raises(ValueError, match=r"you must pass base_url=None"): - AsyncBrowserbase(api_key=api_key, _strict_response_validation=True, environment="production") - - client = AsyncBrowserbase( - base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" - ) - assert str(client.base_url).startswith("https://api.browserbase.com") - @pytest.mark.parametrize( "client", [ @@ -1518,12 +1498,12 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/v1/contexts").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v1/sessions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): await self.client.post( - "/v1/contexts", - body=cast(object, dict(project_id="projectId")), + "/v1/sessions", + body=cast(object, dict(project_id="your_project_id", proxies=True)), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -1533,12 +1513,12 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) @mock.patch("browserbase._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/v1/contexts").mock(return_value=httpx.Response(500)) + respx_mock.post("/v1/sessions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): await self.client.post( - "/v1/contexts", - body=cast(object, dict(project_id="projectId")), + "/v1/sessions", + body=cast(object, dict(project_id="your_project_id", proxies=True)), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -1570,9 +1550,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/contexts").mock(side_effect=retry_handler) + respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.contexts.with_raw_response.create(project_id="projectId") + 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 @@ -1595,9 +1575,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/contexts").mock(side_effect=retry_handler) + respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.contexts.with_raw_response.create( + response = await client.sessions.with_raw_response.create( project_id="projectId", extra_headers={"x-stainless-retry-count": Omit()} ) @@ -1621,9 +1601,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/v1/contexts").mock(side_effect=retry_handler) + respx_mock.post("/v1/sessions").mock(side_effect=retry_handler) - response = await client.contexts.with_raw_response.create( + response = await client.sessions.with_raw_response.create( project_id="projectId", extra_headers={"x-stainless-retry-count": "42"} )