diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4c56f2a4..57726a4f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.64.0" + ".": "0.65.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index d9e97532..dcde96de 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-9d489e3e43edfa64a754d4281241718e01c85d9a82ef3687df12bbd3c4ff5b42.yml -openapi_spec_hash: a953cafb7f40ec8495dbd7df8bab8bad -config_hash: bb7acce8576a50dd449b0c8f58ef0f1d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-08c2d6a44f4cdcbfb6803a3043fdc1a3e33911dec4652cb3a870f01bc584421f.yml +openapi_spec_hash: c816491451347eb93b793cddf6a78648 +config_hash: 9e45c27425021d49b5391f5cc980b046 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4edf1e..18792366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.65.0 (2026-06-08) + +Full Changelog: [v0.64.0...v0.65.0](https://github.com/kernel/kernel-python-sdk/compare/v0.64.0...v0.65.0) + +### Features + +* **api:** allow setting a name and tags on a pool-acquired browser session ([2977911](https://github.com/kernel/kernel-python-sdk/commit/2977911151def0f4193c5e8063ab33800d167585)) +* **api:** support id-or-name lookup on browser session get/patch/delete ([7e6d287](https://github.com/kernel/kernel-python-sdk/commit/7e6d28795b4b2b9f9e14cf14711a72053530b576)) + ## 0.64.0 (2026-06-05) Full Changelog: [v0.63.0...v0.64.0](https://github.com/kernel/kernel-python-sdk/compare/v0.63.0...v0.64.0) diff --git a/api.md b/api.md index c5019550..2d311077 100644 --- a/api.md +++ b/api.md @@ -95,11 +95,11 @@ from kernel.types import ( Methods: - client.browsers.create(\*\*params) -> BrowserCreateResponse -- client.browsers.retrieve(id, \*\*params) -> BrowserRetrieveResponse -- client.browsers.update(id, \*\*params) -> BrowserUpdateResponse +- client.browsers.retrieve(id_or_name, \*\*params) -> BrowserRetrieveResponse +- client.browsers.update(id_or_name, \*\*params) -> BrowserUpdateResponse - client.browsers.list(\*\*params) -> SyncOffsetPagination[BrowserListResponse] - client.browsers.curl(id, \*\*params) -> BrowserCurlResponse -- client.browsers.delete_by_id(id) -> None +- client.browsers.delete_by_id(id_or_name) -> None - client.browsers.load_extensions(id, \*\*params) -> None ## Telemetry diff --git a/pyproject.toml b/pyproject.toml index a312a055..75d7d508 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.64.0" +version = "0.65.0" description = "The official Python library for the kernel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/kernel/_version.py b/src/kernel/_version.py index b55ca852..7a6f830a 100644 --- a/src/kernel/_version.py +++ b/src/kernel/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "kernel" -__version__ = "0.64.0" # x-release-please-version +__version__ = "0.65.0" # x-release-please-version diff --git a/src/kernel/resources/browser_pools.py b/src/kernel/resources/browser_pools.py index 203e22ad..5099f446 100644 --- a/src/kernel/resources/browser_pools.py +++ b/src/kernel/resources/browser_pools.py @@ -26,6 +26,7 @@ ) from ..pagination import SyncOffsetPagination, AsyncOffsetPagination from .._base_client import AsyncPaginator, make_request_options +from ..types.tags_param import TagsParam from ..types.browser_pool import BrowserPool from ..types.browser_pool_acquire_response import BrowserPoolAcquireResponse from ..types.shared_params.browser_profile import BrowserProfile @@ -417,6 +418,8 @@ def acquire( id_or_name: str, *, acquire_timeout_seconds: int | Omit = omit, + name: str | Omit = omit, + tags: TagsParam | 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, @@ -436,6 +439,15 @@ def acquire( calculated time it would take to fill the pool at the currently configured fill rate. + name: Optional human-readable name for the acquired browser session, used to find it + later in the dashboard. Must be unique among active sessions within the pool's + project. Applies to this lease only and is cleared when the browser is released + back to the pool. + + tags: Optional user-defined key-value tags for the acquired browser session, used to + find and group sessions later. Applies to this lease only and are cleared when + the browser is released back to the pool. Up to 50 pairs. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -449,7 +461,11 @@ def acquire( return self._post( path_template("/browser_pools/{id_or_name}/acquire", id_or_name=id_or_name), body=maybe_transform( - {"acquire_timeout_seconds": acquire_timeout_seconds}, + { + "acquire_timeout_seconds": acquire_timeout_seconds, + "name": name, + "tags": tags, + }, browser_pool_acquire_params.BrowserPoolAcquireParams, ), options=make_request_options( @@ -923,6 +939,8 @@ async def acquire( id_or_name: str, *, acquire_timeout_seconds: int | Omit = omit, + name: str | Omit = omit, + tags: TagsParam | 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, @@ -942,6 +960,15 @@ async def acquire( calculated time it would take to fill the pool at the currently configured fill rate. + name: Optional human-readable name for the acquired browser session, used to find it + later in the dashboard. Must be unique among active sessions within the pool's + project. Applies to this lease only and is cleared when the browser is released + back to the pool. + + tags: Optional user-defined key-value tags for the acquired browser session, used to + find and group sessions later. Applies to this lease only and are cleared when + the browser is released back to the pool. Up to 50 pairs. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -955,7 +982,11 @@ async def acquire( return await self._post( path_template("/browser_pools/{id_or_name}/acquire", id_or_name=id_or_name), body=await async_maybe_transform( - {"acquire_timeout_seconds": acquire_timeout_seconds}, + { + "acquire_timeout_seconds": acquire_timeout_seconds, + "name": name, + "tags": tags, + }, browser_pool_acquire_params.BrowserPoolAcquireParams, ), options=make_request_options( diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py index 516eca31..61ada1c2 100644 --- a/src/kernel/resources/browsers/browsers.py +++ b/src/kernel/resources/browsers/browsers.py @@ -292,7 +292,7 @@ def create( def retrieve( self, - id: str, + id_or_name: str, *, include_deleted: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -316,10 +316,10 @@ def retrieve( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._get( - path_template("/browsers/{id}", id=id), + path_template("/browsers/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -334,7 +334,7 @@ def retrieve( def update( self, - id: str, + id_or_name: str, *, disable_default_proxy: bool | Omit = omit, profile: BrowserProfile | Omit = omit, @@ -377,10 +377,10 @@ def update( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return self._patch( - path_template("/browsers/{id}", id=id), + path_template("/browsers/{id_or_name}", id_or_name=id_or_name), body=maybe_transform( { "disable_default_proxy": disable_default_proxy, @@ -590,7 +590,7 @@ def stream( def delete_by_id( self, - id: str, + id_or_name: 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. @@ -600,7 +600,7 @@ def delete_by_id( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete a browser session by ID + Delete a browser session by ID or name Args: extra_headers: Send extra headers @@ -611,11 +611,11 @@ def delete_by_id( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - path_template("/browsers/{id}", id=id), + path_template("/browsers/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -857,7 +857,7 @@ async def create( async def retrieve( self, - id: str, + id_or_name: str, *, include_deleted: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -881,10 +881,10 @@ async def retrieve( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._get( - path_template("/browsers/{id}", id=id), + path_template("/browsers/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -899,7 +899,7 @@ async def retrieve( async def update( self, - id: str, + id_or_name: str, *, disable_default_proxy: bool | Omit = omit, profile: BrowserProfile | Omit = omit, @@ -942,10 +942,10 @@ async def update( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") return await self._patch( - path_template("/browsers/{id}", id=id), + path_template("/browsers/{id_or_name}", id_or_name=id_or_name), body=await async_maybe_transform( { "disable_default_proxy": disable_default_proxy, @@ -1155,7 +1155,7 @@ async def stream( async def delete_by_id( self, - id: str, + id_or_name: 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. @@ -1165,7 +1165,7 @@ async def delete_by_id( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: """ - Delete a browser session by ID + Delete a browser session by ID or name Args: extra_headers: Send extra headers @@ -1176,11 +1176,11 @@ async def delete_by_id( 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}") + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - path_template("/browsers/{id}", id=id), + path_template("/browsers/{id_or_name}", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/kernel/types/browser_pool_acquire_params.py b/src/kernel/types/browser_pool_acquire_params.py index d0df921a..022d69bd 100644 --- a/src/kernel/types/browser_pool_acquire_params.py +++ b/src/kernel/types/browser_pool_acquire_params.py @@ -4,6 +4,8 @@ from typing_extensions import TypedDict +from .tags_param import TagsParam + __all__ = ["BrowserPoolAcquireParams"] @@ -14,3 +16,18 @@ class BrowserPoolAcquireParams(TypedDict, total=False): Defaults to the calculated time it would take to fill the pool at the currently configured fill rate. """ + + name: str + """ + Optional human-readable name for the acquired browser session, used to find it + later in the dashboard. Must be unique among active sessions within the pool's + project. Applies to this lease only and is cleared when the browser is released + back to the pool. + """ + + tags: TagsParam + """ + Optional user-defined key-value tags for the acquired browser session, used to + find and group sessions later. Applies to this lease only and are cleared when + the browser is released back to the pool. Up to 50 pairs. + """ diff --git a/tests/api_resources/test_browser_pools.py b/tests/api_resources/test_browser_pools.py index c71e6c27..e6f8b853 100644 --- a/tests/api_resources/test_browser_pools.py +++ b/tests/api_resources/test_browser_pools.py @@ -309,6 +309,11 @@ def test_method_acquire_with_all_params(self, client: Kernel) -> None: browser_pool = client.browser_pools.acquire( id_or_name="id_or_name", acquire_timeout_seconds=0, + name="checkout-flow-1", + tags={ + "team": "backend", + "env": "staging", + }, ) assert_matches_type(BrowserPoolAcquireResponse, browser_pool, path=["response"]) @@ -738,6 +743,11 @@ async def test_method_acquire_with_all_params(self, async_client: AsyncKernel) - browser_pool = await async_client.browser_pools.acquire( id_or_name="id_or_name", acquire_timeout_seconds=0, + name="checkout-flow-1", + tags={ + "team": "backend", + "env": "staging", + }, ) assert_matches_type(BrowserPoolAcquireResponse, browser_pool, path=["response"]) diff --git a/tests/api_resources/test_browsers.py b/tests/api_resources/test_browsers.py index 5284214a..e09d4705 100644 --- a/tests/api_resources/test_browsers.py +++ b/tests/api_resources/test_browsers.py @@ -107,7 +107,7 @@ def test_streaming_response_create(self, client: Kernel) -> None: @parametrize def test_method_retrieve(self, client: Kernel) -> None: browser = client.browsers.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert_matches_type(BrowserRetrieveResponse, browser, path=["response"]) @@ -115,7 +115,7 @@ def test_method_retrieve(self, client: Kernel) -> None: @parametrize def test_method_retrieve_with_all_params(self, client: Kernel) -> None: browser = client.browsers.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", include_deleted=True, ) assert_matches_type(BrowserRetrieveResponse, browser, path=["response"]) @@ -124,7 +124,7 @@ def test_method_retrieve_with_all_params(self, client: Kernel) -> None: @parametrize def test_raw_response_retrieve(self, client: Kernel) -> None: response = client.browsers.with_raw_response.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert response.is_closed is True @@ -136,7 +136,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @parametrize def test_streaming_response_retrieve(self, client: Kernel) -> None: with client.browsers.with_streaming_response.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -149,16 +149,16 @@ def test_streaming_response_retrieve(self, client: Kernel) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_retrieve(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.browsers.with_raw_response.retrieve( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_update(self, client: Kernel) -> None: browser = client.browsers.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert_matches_type(BrowserUpdateResponse, browser, path=["response"]) @@ -166,7 +166,7 @@ def test_method_update(self, client: Kernel) -> None: @parametrize def test_method_update_with_all_params(self, client: Kernel) -> None: browser = client.browsers.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", disable_default_proxy=True, profile={ "id": "id", @@ -201,7 +201,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: @parametrize def test_raw_response_update(self, client: Kernel) -> None: response = client.browsers.with_raw_response.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert response.is_closed is True @@ -213,7 +213,7 @@ def test_raw_response_update(self, client: Kernel) -> None: @parametrize def test_streaming_response_update(self, client: Kernel) -> None: with client.browsers.with_streaming_response.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -226,9 +226,9 @@ def test_streaming_response_update(self, client: Kernel) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_update(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.browsers.with_raw_response.update( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -369,7 +369,7 @@ def test_streaming_response_delete_by_id(self, client: Kernel) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_delete_by_id(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): client.browsers.with_raw_response.delete_by_id( "", ) @@ -529,7 +529,7 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: browser = await async_client.browsers.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert_matches_type(BrowserRetrieveResponse, browser, path=["response"]) @@ -537,7 +537,7 @@ async def test_method_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel) -> None: browser = await async_client.browsers.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", include_deleted=True, ) assert_matches_type(BrowserRetrieveResponse, browser, path=["response"]) @@ -546,7 +546,7 @@ async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel) @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: response = await async_client.browsers.with_raw_response.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert response.is_closed is True @@ -558,7 +558,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> None: async with async_client.browsers.with_streaming_response.retrieve( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -571,16 +571,16 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> N @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.browsers.with_raw_response.retrieve( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_update(self, async_client: AsyncKernel) -> None: browser = await async_client.browsers.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert_matches_type(BrowserUpdateResponse, browser, path=["response"]) @@ -588,7 +588,7 @@ async def test_method_update(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None: browser = await async_client.browsers.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", disable_default_proxy=True, profile={ "id": "id", @@ -623,7 +623,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> @parametrize async def test_raw_response_update(self, async_client: AsyncKernel) -> None: response = await async_client.browsers.with_raw_response.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) assert response.is_closed is True @@ -635,7 +635,7 @@ async def test_raw_response_update(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_update(self, async_client: AsyncKernel) -> None: async with async_client.browsers.with_streaming_response.update( - id="htzv5orfit78e1m2biiifpbv", + id_or_name="htzv5orfit78e1m2biiifpbv", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -648,9 +648,9 @@ async def test_streaming_response_update(self, async_client: AsyncKernel) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_update(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.browsers.with_raw_response.update( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -791,7 +791,7 @@ async def test_streaming_response_delete_by_id(self, async_client: AsyncKernel) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_delete_by_id(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): await async_client.browsers.with_raw_response.delete_by_id( "", )