Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.81.0"
".": "0.82.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 127
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-1dffe057847b596dd311ce4b6e0e8036126dce3e97d390636c9c8e6d2f4823e1.yml
openapi_spec_hash: c06336c199ee64c222ba3122deae9463
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-5163e8941e42f7fb84e10f572f538202b31722c84bb980db74ccd44a960eb87f.yml
openapi_spec_hash: 8aa974cf7843681d6ae3e6dcf099596d
config_hash: 77ee715aa17061166f9a02b264a21b8d
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.82.0 (2026-07-23)

Full Changelog: [v0.81.0...v0.82.0](https://github.com/kernel/kernel-python-sdk/compare/v0.81.0...v0.82.0)

### Features

* Count project names by Unicode code point ([cecdf6d](https://github.com/kernel/kernel-python-sdk/commit/cecdf6d4a4ef7919b6eef5d03f76542b6ade0906))
* Stream profile downloads as tar archives ([3a2eb97](https://github.com/kernel/kernel-python-sdk/commit/3a2eb97686f3ecf02db16d7bccea803476cb730f))

## 0.81.0 (2026-07-21)

Full Changelog: [v0.80.0...v0.81.0](https://github.com/kernel/kernel-python-sdk/compare/v0.80.0...v0.81.0)
Expand Down
2 changes: 1 addition & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ Methods:
- <code title="patch /profiles/{id_or_name}">client.profiles.<a href="./src/kernel/resources/profiles.py">update</a>(id_or_name, \*\*<a href="src/kernel/types/profile_update_params.py">params</a>) -> <a href="./src/kernel/types/profile.py">Profile</a></code>
- <code title="get /profiles">client.profiles.<a href="./src/kernel/resources/profiles.py">list</a>(\*\*<a href="src/kernel/types/profile_list_params.py">params</a>) -> <a href="./src/kernel/types/profile.py">SyncOffsetPagination[Profile]</a></code>
- <code title="delete /profiles/{id_or_name}">client.profiles.<a href="./src/kernel/resources/profiles.py">delete</a>(id_or_name) -> None</code>
- <code title="get /profiles/{id_or_name}/download">client.profiles.<a href="./src/kernel/resources/profiles.py">download</a>(id_or_name) -> BinaryAPIResponse</code>
- <code title="get /profiles/{id_or_name}/download">client.profiles.<a href="./src/kernel/resources/profiles.py">download</a>(id_or_name, \*\*<a href="src/kernel/types/profile_download_params.py">params</a>) -> BinaryAPIResponse</code>

# Auth

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kernel"
version = "0.81.0"
version = "0.82.0"
description = "The official Python library for the kernel API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/kernel/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "kernel"
__version__ = "0.81.0" # x-release-please-version
__version__ = "0.82.0" # x-release-please-version
44 changes: 35 additions & 9 deletions src/kernel/resources/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from __future__ import annotations

from typing_extensions import Literal

import httpx

from ..types import profile_list_params, profile_create_params, profile_update_params
from ..types import profile_list_params, profile_create_params, profile_update_params, profile_download_params
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from .._utils import path_template, maybe_transform, async_maybe_transform
from .._compat import cached_property
Expand Down Expand Up @@ -264,17 +266,25 @@ def download(
self,
id_or_name: str,
*,
format: Literal["tar.zst", "tar"] | 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,
) -> BinaryAPIResponse:
"""
Returns a zstd-compressed tar file of the full user-data directory.
"""Downloads the profile in its stored format by default.

Current profiles are
returned as zstd-compressed tar archives, while legacy profiles remain JSON. Set
`format=tar` to decompress current profiles during download; legacy profiles
remain JSON.

Args:
format: Response format for current profile archives. Legacy profiles are always
returned as JSON.

extra_headers: Send extra headers

extra_query: Add additional query parameters to the request
Expand All @@ -285,11 +295,15 @@ def download(
"""
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": "application/octet-stream", **(extra_headers or {})}
extra_headers = {"Accept": "application/zstd", **(extra_headers or {})}
return self._get(
path_template("/profiles/{id_or_name}/download", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=maybe_transform({"format": format}, profile_download_params.ProfileDownloadParams),
),
cast_to=BinaryAPIResponse,
)
Expand Down Expand Up @@ -529,17 +543,25 @@ async def download(
self,
id_or_name: str,
*,
format: Literal["tar.zst", "tar"] | 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,
) -> AsyncBinaryAPIResponse:
"""
Returns a zstd-compressed tar file of the full user-data directory.
"""Downloads the profile in its stored format by default.

Current profiles are
returned as zstd-compressed tar archives, while legacy profiles remain JSON. Set
`format=tar` to decompress current profiles during download; legacy profiles
remain JSON.

Args:
format: Response format for current profile archives. Legacy profiles are always
returned as JSON.

extra_headers: Send extra headers

extra_query: Add additional query parameters to the request
Expand All @@ -550,11 +572,15 @@ async def download(
"""
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": "application/octet-stream", **(extra_headers or {})}
extra_headers = {"Accept": "application/zstd", **(extra_headers or {})}
return await self._get(
path_template("/profiles/{id_or_name}/download", id_or_name=id_or_name),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform({"format": format}, profile_download_params.ProfileDownloadParams),
),
cast_to=AsyncBinaryAPIResponse,
)
Expand Down
8 changes: 4 additions & 4 deletions src/kernel/resources/projects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def create(
Create a new project within the authenticated organization.

Args:
name: Project name (1-255 characters)
name: Project name (1-255 Unicode code points)

extra_headers: Send extra headers

Expand Down Expand Up @@ -144,7 +144,7 @@ def update(
Update a project's name or status.

Args:
name: New project name
name: New project name (1-255 Unicode code points)

status: New project status

Expand Down Expand Up @@ -307,7 +307,7 @@ async def create(
Create a new project within the authenticated organization.

Args:
name: Project name (1-255 characters)
name: Project name (1-255 Unicode code points)

extra_headers: Send extra headers

Expand Down Expand Up @@ -377,7 +377,7 @@ async def update(
Update a project's name or status.

Args:
name: New project name
name: New project name (1-255 Unicode code points)

status: New project status

Expand Down
1 change: 1 addition & 0 deletions src/kernel/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from .browser_update_response import BrowserUpdateResponse as BrowserUpdateResponse
from .extension_list_response import ExtensionListResponse as ExtensionListResponse
from .extension_upload_params import ExtensionUploadParams as ExtensionUploadParams
from .profile_download_params import ProfileDownloadParams as ProfileDownloadParams
from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse
from .browser_pool_list_params import BrowserPoolListParams as BrowserPoolListParams
from .credential_create_params import CredentialCreateParams as CredentialCreateParams
Expand Down
15 changes: 15 additions & 0 deletions src/kernel/types/profile_download_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

from typing_extensions import Literal, TypedDict

__all__ = ["ProfileDownloadParams"]


class ProfileDownloadParams(TypedDict, total=False):
format: Literal["tar.zst", "tar"]
"""Response format for current profile archives.

Legacy profiles are always returned as JSON.
"""
2 changes: 1 addition & 1 deletion src/kernel/types/project_create_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

class ProjectCreateParams(TypedDict, total=False):
name: Required[str]
"""Project name (1-255 characters)"""
"""Project name (1-255 Unicode code points)"""
2 changes: 1 addition & 1 deletion src/kernel/types/project_update_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class ProjectUpdateParams(TypedDict, total=False):
name: str
"""New project name"""
"""New project name (1-255 Unicode code points)"""

status: Literal["active", "archived"]
"""New project status"""
46 changes: 37 additions & 9 deletions tests/api_resources/test_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

from kernel import Kernel, AsyncKernel
from tests.utils import assert_matches_type
from kernel.types import Profile
from kernel.types import (
Profile,
)
from kernel._response import (
BinaryAPIResponse,
AsyncBinaryAPIResponse,
Expand Down Expand Up @@ -236,7 +238,20 @@ def test_path_params_delete(self, client: Kernel) -> None:
def test_method_download(self, client: Kernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = client.profiles.download(
"id_or_name",
id_or_name="id_or_name",
)
assert profile.is_closed
assert profile.json() == {"foo": "bar"}
assert cast(Any, profile.is_closed) is True
assert isinstance(profile, BinaryAPIResponse)

@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_download_with_all_params(self, client: Kernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = client.profiles.download(
id_or_name="id_or_name",
format="tar.zst",
)
assert profile.is_closed
assert profile.json() == {"foo": "bar"}
Expand All @@ -249,7 +264,7 @@ def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) ->
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))

profile = client.profiles.with_raw_response.download(
"id_or_name",
id_or_name="id_or_name",
)

assert profile.is_closed is True
Expand All @@ -262,7 +277,7 @@ def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) ->
def test_streaming_response_download(self, client: Kernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
with client.profiles.with_streaming_response.download(
"id_or_name",
id_or_name="id_or_name",
) as profile:
assert not profile.is_closed
assert profile.http_request.headers.get("X-Stainless-Lang") == "python"
Expand All @@ -278,7 +293,7 @@ def test_streaming_response_download(self, client: Kernel, respx_mock: MockRoute
def test_path_params_download(self, client: Kernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
client.profiles.with_raw_response.download(
"",
id_or_name="",
)


Expand Down Expand Up @@ -497,7 +512,20 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None:
async def test_method_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = await async_client.profiles.download(
"id_or_name",
id_or_name="id_or_name",
)
assert profile.is_closed
assert await profile.json() == {"foo": "bar"}
assert cast(Any, profile.is_closed) is True
assert isinstance(profile, AsyncBinaryAPIResponse)

@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_method_download_with_all_params(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
profile = await async_client.profiles.download(
id_or_name="id_or_name",
format="tar.zst",
)
assert profile.is_closed
assert await profile.json() == {"foo": "bar"}
Expand All @@ -510,7 +538,7 @@ async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))

profile = await async_client.profiles.with_raw_response.download(
"id_or_name",
id_or_name="id_or_name",
)

assert profile.is_closed is True
Expand All @@ -523,7 +551,7 @@ async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock
async def test_streaming_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None:
respx_mock.get("/profiles/id_or_name/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
async with async_client.profiles.with_streaming_response.download(
"id_or_name",
id_or_name="id_or_name",
) as profile:
assert not profile.is_closed
assert profile.http_request.headers.get("X-Stainless-Lang") == "python"
Expand All @@ -539,5 +567,5 @@ async def test_streaming_response_download(self, async_client: AsyncKernel, resp
async def test_path_params_download(self, async_client: AsyncKernel) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"):
await async_client.profiles.with_raw_response.download(
"",
id_or_name="",
)
4 changes: 2 additions & 2 deletions tests/api_resources/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_method_update(self, client: Kernel) -> None:
def test_method_update_with_all_params(self, client: Kernel) -> None:
project = client.projects.update(
id="id",
name="name",
name="x",
status="active",
)
assert_matches_type(Project, project, path=["response"])
Expand Down Expand Up @@ -322,7 +322,7 @@ async def test_method_update(self, async_client: AsyncKernel) -> None:
async def test_method_update_with_all_params(self, async_client: AsyncKernel) -> None:
project = await async_client.projects.update(
id="id",
name="name",
name="x",
status="active",
)
assert_matches_type(Project, project, path=["response"])
Expand Down
Loading