From 67970b2b8cc0312b81d10e6fec6c3a016c79d347 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:15:11 +0000 Subject: [PATCH 1/3] feat: Support project selection by ID or name Stainless-Generated-From: edf469a448508ce7f57fbabeded1c15af42c3d8c --- api.md | 10 ++-- src/kernel/_client.py | 14 ++++++ src/kernel/resources/projects/limits.py | 32 ++++++------ src/kernel/resources/projects/projects.py | 56 ++++++++++----------- src/kernel/types/project_create_params.py | 2 +- src/kernel/types/project_update_params.py | 2 +- tests/api_resources/projects/test_limits.py | 40 +++++++-------- tests/api_resources/test_projects.py | 56 ++++++++++----------- 8 files changed, 113 insertions(+), 99 deletions(-) diff --git a/api.md b/api.md index b991db92..d442a3d0 100644 --- a/api.md +++ b/api.md @@ -432,10 +432,10 @@ from kernel.types import CreateProjectRequest, Project, UpdateProjectRequest Methods: - client.projects.create(\*\*params) -> Project -- client.projects.retrieve(id) -> Project -- client.projects.update(id, \*\*params) -> Project +- client.projects.retrieve(id_or_name) -> Project +- client.projects.update(id_or_name, \*\*params) -> Project - client.projects.list(\*\*params) -> SyncOffsetPagination[Project] -- client.projects.delete(id) -> None +- client.projects.delete(id_or_name) -> None ## Limits @@ -447,8 +447,8 @@ from kernel.types.projects import ProjectLimits, UpdateProjectLimitsRequest Methods: -- client.projects.limits.retrieve(id) -> ProjectLimits -- client.projects.limits.update(id, \*\*params) -> ProjectLimits +- client.projects.limits.retrieve(id_or_name) -> ProjectLimits +- client.projects.limits.update(id_or_name, \*\*params) -> ProjectLimits # Organization diff --git a/src/kernel/_client.py b/src/kernel/_client.py index 62bd2014..eb4d1af1 100644 --- a/src/kernel/_client.py +++ b/src/kernel/_client.py @@ -103,6 +103,7 @@ class Kernel(SyncAPIClient): browser_route_cache: BrowserRouteCache project_id: str | None + project: str | None _environment: Literal["production", "development"] | NotGiven _browser_routing: BrowserRoutingConfig @@ -112,6 +113,7 @@ def __init__( *, api_key: str | None = None, project_id: str | None = None, + project: str | None = None, environment: Literal["production", "development"] | NotGiven = not_given, base_url: str | httpx.URL | None | NotGiven = not_given, timeout: float | Timeout | None | NotGiven = not_given, @@ -147,6 +149,8 @@ def __init__( self.project_id = project_id + self.project = project + self._environment = environment base_url_env = os.environ.get("KERNEL_BASE_URL") @@ -328,6 +332,7 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": "false", "X-Kernel-Project-Id": self.project_id if self.project_id is not None else Omit(), + "X-Kernel-Project": self.project if self.project is not None else Omit(), **self._custom_headers, } @@ -367,6 +372,7 @@ def copy( *, api_key: str | None = None, project_id: str | None = None, + project: str | None = None, environment: Literal["production", "development"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, @@ -404,6 +410,7 @@ def copy( return self.__class__( api_key=api_key or self.api_key, project_id=project_id or self.project_id, + project=project or self.project, base_url=base_url or self.base_url, environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, @@ -459,6 +466,7 @@ class AsyncKernel(AsyncAPIClient): browser_route_cache: BrowserRouteCache project_id: str | None + project: str | None _environment: Literal["production", "development"] | NotGiven _browser_routing: BrowserRoutingConfig @@ -468,6 +476,7 @@ def __init__( *, api_key: str | None = None, project_id: str | None = None, + project: str | None = None, environment: Literal["production", "development"] | NotGiven = not_given, base_url: str | httpx.URL | None | NotGiven = not_given, timeout: float | Timeout | None | NotGiven = not_given, @@ -503,6 +512,8 @@ def __init__( self.project_id = project_id + self.project = project + self._environment = environment base_url_env = os.environ.get("KERNEL_BASE_URL") @@ -684,6 +695,7 @@ def default_headers(self) -> dict[str, str | Omit]: **super().default_headers, "X-Stainless-Async": f"async:{get_async_library()}", "X-Kernel-Project-Id": self.project_id if self.project_id is not None else Omit(), + "X-Kernel-Project": self.project if self.project is not None else Omit(), **self._custom_headers, } @@ -723,6 +735,7 @@ def copy( *, api_key: str | None = None, project_id: str | None = None, + project: str | None = None, environment: Literal["production", "development"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, @@ -760,6 +773,7 @@ def copy( return self.__class__( api_key=api_key or self.api_key, project_id=project_id or self.project_id, + project=project or self.project, base_url=base_url or self.base_url, environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, diff --git a/src/kernel/resources/projects/limits.py b/src/kernel/resources/projects/limits.py index 87227768..ef95b4d0 100644 --- a/src/kernel/resources/projects/limits.py +++ b/src/kernel/resources/projects/limits.py @@ -51,7 +51,7 @@ def with_streaming_response(self) -> LimitsResourceWithStreamingResponse: def retrieve( 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. @@ -74,10 +74,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("/org/projects/{id}/limits", id=id), + path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -86,7 +86,7 @@ def retrieve( def update( self, - id: str, + id_or_name: str, *, max_concurrent_invocations: Optional[int] | Omit = omit, max_concurrent_sessions: Optional[int] | Omit = omit, @@ -123,10 +123,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("/org/projects/{id}/limits", id=id), + path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name), body=maybe_transform( { "max_concurrent_invocations": max_concurrent_invocations, @@ -170,7 +170,7 @@ def with_streaming_response(self) -> AsyncLimitsResourceWithStreamingResponse: async def retrieve( 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. @@ -193,10 +193,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("/org/projects/{id}/limits", id=id), + path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -205,7 +205,7 @@ async def retrieve( async def update( self, - id: str, + id_or_name: str, *, max_concurrent_invocations: Optional[int] | Omit = omit, max_concurrent_sessions: Optional[int] | Omit = omit, @@ -242,10 +242,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("/org/projects/{id}/limits", id=id), + path_template("/org/projects/{id_or_name}/limits", id_or_name=id_or_name), body=await async_maybe_transform( { "max_concurrent_invocations": max_concurrent_invocations, diff --git a/src/kernel/resources/projects/projects.py b/src/kernel/resources/projects/projects.py index 2c3d3569..ca519a75 100644 --- a/src/kernel/resources/projects/projects.py +++ b/src/kernel/resources/projects/projects.py @@ -82,7 +82,7 @@ def create( Create a new project within the authenticated organization. Args: - name: Project name (1-255 Unicode code points) + name: Project name (1-255 Unicode code points; cannot contain `/` or `%`) extra_headers: Send extra headers @@ -103,7 +103,7 @@ def create( def retrieve( 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. @@ -125,10 +125,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("/org/projects/{id}", id=id), + path_template("/org/projects/{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 ), @@ -137,7 +137,7 @@ def retrieve( def update( self, - id: str, + id_or_name: str, *, name: str | Omit = omit, status: Literal["active", "archived"] | Omit = omit, @@ -152,7 +152,7 @@ def update( Update a project's name or status. Args: - name: New project name (1-255 Unicode code points) + name: New project name (1-255 Unicode code points; cannot contain `/` or `%`) status: New project status @@ -164,10 +164,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("/org/projects/{id}", id=id), + path_template("/org/projects/{id_or_name}", id_or_name=id_or_name), body=maybe_transform( { "name": name, @@ -239,7 +239,7 @@ def list( def delete( 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. @@ -261,11 +261,11 @@ def delete( 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("/org/projects/{id}", id=id), + path_template("/org/projects/{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 ), @@ -323,7 +323,7 @@ async def create( Create a new project within the authenticated organization. Args: - name: Project name (1-255 Unicode code points) + name: Project name (1-255 Unicode code points; cannot contain `/` or `%`) extra_headers: Send extra headers @@ -344,7 +344,7 @@ async def create( async def retrieve( 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. @@ -366,10 +366,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("/org/projects/{id}", id=id), + path_template("/org/projects/{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 ), @@ -378,7 +378,7 @@ async def retrieve( async def update( self, - id: str, + id_or_name: str, *, name: str | Omit = omit, status: Literal["active", "archived"] | Omit = omit, @@ -393,7 +393,7 @@ async def update( Update a project's name or status. Args: - name: New project name (1-255 Unicode code points) + name: New project name (1-255 Unicode code points; cannot contain `/` or `%`) status: New project status @@ -405,10 +405,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("/org/projects/{id}", id=id), + path_template("/org/projects/{id_or_name}", id_or_name=id_or_name), body=await async_maybe_transform( { "name": name, @@ -480,7 +480,7 @@ def list( async def delete( 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. @@ -502,11 +502,11 @@ async def delete( 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("/org/projects/{id}", id=id), + path_template("/org/projects/{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/project_create_params.py b/src/kernel/types/project_create_params.py index 9c51c570..664e89bc 100644 --- a/src/kernel/types/project_create_params.py +++ b/src/kernel/types/project_create_params.py @@ -9,4 +9,4 @@ class ProjectCreateParams(TypedDict, total=False): name: Required[str] - """Project name (1-255 Unicode code points)""" + """Project name (1-255 Unicode code points; cannot contain `/` or `%`)""" diff --git a/src/kernel/types/project_update_params.py b/src/kernel/types/project_update_params.py index 7b4359ff..3d93cef4 100644 --- a/src/kernel/types/project_update_params.py +++ b/src/kernel/types/project_update_params.py @@ -9,7 +9,7 @@ class ProjectUpdateParams(TypedDict, total=False): name: str - """New project name (1-255 Unicode code points)""" + """New project name (1-255 Unicode code points; cannot contain `/` or `%`)""" status: Literal["active", "archived"] """New project status""" diff --git a/tests/api_resources/projects/test_limits.py b/tests/api_resources/projects/test_limits.py index a42a0bde..e8791c30 100644 --- a/tests/api_resources/projects/test_limits.py +++ b/tests/api_resources/projects/test_limits.py @@ -21,7 +21,7 @@ class TestLimits: @parametrize def test_method_retrieve(self, client: Kernel) -> None: limit = client.projects.limits.retrieve( - "id", + "id_or_name", ) assert_matches_type(ProjectLimits, limit, path=["response"]) @@ -29,7 +29,7 @@ def test_method_retrieve(self, client: Kernel) -> None: @parametrize def test_raw_response_retrieve(self, client: Kernel) -> None: response = client.projects.limits.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -41,7 +41,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @parametrize def test_streaming_response_retrieve(self, client: Kernel) -> None: with client.projects.limits.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -54,7 +54,7 @@ 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.projects.limits.with_raw_response.retrieve( "", ) @@ -63,7 +63,7 @@ def test_path_params_retrieve(self, client: Kernel) -> None: @parametrize def test_method_update(self, client: Kernel) -> None: limit = client.projects.limits.update( - id="id", + id_or_name="id_or_name", ) assert_matches_type(ProjectLimits, limit, path=["response"]) @@ -71,7 +71,7 @@ def test_method_update(self, client: Kernel) -> None: @parametrize def test_method_update_with_all_params(self, client: Kernel) -> None: limit = client.projects.limits.update( - id="id", + id_or_name="id_or_name", max_concurrent_invocations=0, max_concurrent_sessions=0, max_pooled_sessions=0, @@ -82,7 +82,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: @parametrize def test_raw_response_update(self, client: Kernel) -> None: response = client.projects.limits.with_raw_response.update( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -94,7 +94,7 @@ def test_raw_response_update(self, client: Kernel) -> None: @parametrize def test_streaming_response_update(self, client: Kernel) -> None: with client.projects.limits.with_streaming_response.update( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -107,9 +107,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.projects.limits.with_raw_response.update( - id="", + id_or_name="", ) @@ -122,7 +122,7 @@ class TestAsyncLimits: @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: limit = await async_client.projects.limits.retrieve( - "id", + "id_or_name", ) assert_matches_type(ProjectLimits, limit, path=["response"]) @@ -130,7 +130,7 @@ async def test_method_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: response = await async_client.projects.limits.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -142,7 +142,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.projects.limits.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -155,7 +155,7 @@ 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.projects.limits.with_raw_response.retrieve( "", ) @@ -164,7 +164,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_update(self, async_client: AsyncKernel) -> None: limit = await async_client.projects.limits.update( - id="id", + id_or_name="id_or_name", ) assert_matches_type(ProjectLimits, limit, path=["response"]) @@ -172,7 +172,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: limit = await async_client.projects.limits.update( - id="id", + id_or_name="id_or_name", max_concurrent_invocations=0, max_concurrent_sessions=0, max_pooled_sessions=0, @@ -183,7 +183,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.projects.limits.with_raw_response.update( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -195,7 +195,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.projects.limits.with_streaming_response.update( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -208,7 +208,7 @@ 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.projects.limits.with_raw_response.update( - id="", + id_or_name="", ) diff --git a/tests/api_resources/test_projects.py b/tests/api_resources/test_projects.py index e8d98c29..6eaf3b62 100644 --- a/tests/api_resources/test_projects.py +++ b/tests/api_resources/test_projects.py @@ -56,7 +56,7 @@ def test_streaming_response_create(self, client: Kernel) -> None: @parametrize def test_method_retrieve(self, client: Kernel) -> None: project = client.projects.retrieve( - "id", + "id_or_name", ) assert_matches_type(Project, project, path=["response"]) @@ -64,7 +64,7 @@ def test_method_retrieve(self, client: Kernel) -> None: @parametrize def test_raw_response_retrieve(self, client: Kernel) -> None: response = client.projects.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -76,7 +76,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @parametrize def test_streaming_response_retrieve(self, client: Kernel) -> None: with client.projects.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -89,7 +89,7 @@ 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.projects.with_raw_response.retrieve( "", ) @@ -98,7 +98,7 @@ def test_path_params_retrieve(self, client: Kernel) -> None: @parametrize def test_method_update(self, client: Kernel) -> None: project = client.projects.update( - id="id", + id_or_name="id_or_name", ) assert_matches_type(Project, project, path=["response"]) @@ -106,7 +106,7 @@ def test_method_update(self, client: Kernel) -> None: @parametrize def test_method_update_with_all_params(self, client: Kernel) -> None: project = client.projects.update( - id="id", + id_or_name="id_or_name", name="x", status="active", ) @@ -116,7 +116,7 @@ def test_method_update_with_all_params(self, client: Kernel) -> None: @parametrize def test_raw_response_update(self, client: Kernel) -> None: response = client.projects.with_raw_response.update( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -128,7 +128,7 @@ def test_raw_response_update(self, client: Kernel) -> None: @parametrize def test_streaming_response_update(self, client: Kernel) -> None: with client.projects.with_streaming_response.update( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -141,9 +141,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.projects.with_raw_response.update( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -189,7 +189,7 @@ def test_streaming_response_list(self, client: Kernel) -> None: @parametrize def test_method_delete(self, client: Kernel) -> None: project = client.projects.delete( - "id", + "id_or_name", ) assert project is None @@ -197,7 +197,7 @@ def test_method_delete(self, client: Kernel) -> None: @parametrize def test_raw_response_delete(self, client: Kernel) -> None: response = client.projects.with_raw_response.delete( - "id", + "id_or_name", ) assert response.is_closed is True @@ -209,7 +209,7 @@ def test_raw_response_delete(self, client: Kernel) -> None: @parametrize def test_streaming_response_delete(self, client: Kernel) -> None: with client.projects.with_streaming_response.delete( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -222,7 +222,7 @@ def test_streaming_response_delete(self, client: Kernel) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_path_params_delete(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.projects.with_raw_response.delete( "", ) @@ -271,7 +271,7 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: project = await async_client.projects.retrieve( - "id", + "id_or_name", ) assert_matches_type(Project, project, path=["response"]) @@ -279,7 +279,7 @@ async def test_method_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_retrieve(self, async_client: AsyncKernel) -> None: response = await async_client.projects.with_raw_response.retrieve( - "id", + "id_or_name", ) assert response.is_closed is True @@ -291,7 +291,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.projects.with_streaming_response.retrieve( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -304,7 +304,7 @@ 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.projects.with_raw_response.retrieve( "", ) @@ -313,7 +313,7 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_update(self, async_client: AsyncKernel) -> None: project = await async_client.projects.update( - id="id", + id_or_name="id_or_name", ) assert_matches_type(Project, project, path=["response"]) @@ -321,7 +321,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: project = await async_client.projects.update( - id="id", + id_or_name="id_or_name", name="x", status="active", ) @@ -331,7 +331,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.projects.with_raw_response.update( - id="id", + id_or_name="id_or_name", ) assert response.is_closed is True @@ -343,7 +343,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.projects.with_streaming_response.update( - id="id", + id_or_name="id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -356,9 +356,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.projects.with_raw_response.update( - id="", + id_or_name="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -404,7 +404,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_delete(self, async_client: AsyncKernel) -> None: project = await async_client.projects.delete( - "id", + "id_or_name", ) assert project is None @@ -412,7 +412,7 @@ async def test_method_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: response = await async_client.projects.with_raw_response.delete( - "id", + "id_or_name", ) assert response.is_closed is True @@ -424,7 +424,7 @@ async def test_raw_response_delete(self, async_client: AsyncKernel) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncKernel) -> None: async with async_client.projects.with_streaming_response.delete( - "id", + "id_or_name", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -437,7 +437,7 @@ async def test_streaming_response_delete(self, async_client: AsyncKernel) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_path_params_delete(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.projects.with_raw_response.delete( "", ) From 3bb6956e8021a955cfcbd12fd93d2a3a6b648ed7 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 23:31:18 +0000 Subject: [PATCH 2/3] feat: Preserve canonical managed auth input metadata Stainless-Generated-From: 99ae9e331b3ab1c83b6142638f9272eda6a5c06b --- .../types/auth/connection_follow_response.py | 18 ++++++++++++++++++ src/kernel/types/auth/managed_auth.py | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/kernel/types/auth/connection_follow_response.py b/src/kernel/types/auth/connection_follow_response.py index 7a0ecd05..c411c1ba 100644 --- a/src/kernel/types/auth/connection_follow_response.py +++ b/src/kernel/types/auth/connection_follow_response.py @@ -35,9 +35,24 @@ class ManagedAuthStateEventChoice(BaseModel): ] """Choice type.""" + context: Optional[str] = None + """Context captured for a choice.""" + description: Optional[str] = None """Additional context for the choice.""" + display_text: Optional[str] = None + """Display text captured for a choice.""" + + masked_destination: Optional[str] = None + """Masked phone number or email address shown for an MFA choice.""" + + mfa_type: Optional[Literal["sms", "call", "email", "totp", "push", "password", "passkey", "switch", "other"]] = None + """Semantic MFA method. + + Choice id remains the stable identity of the exact option selected. + """ + observed_selector: Optional[str] = None """Selector for the visible choice, when available.""" @@ -88,6 +103,9 @@ class ManagedAuthStateEventField(BaseModel): type: Literal["identifier", "password", "code", "totp_code", "totp_secret", "text"] """Managed-auth field type.""" + hint: Optional[str] = None + """Context shown near the field, including a masked code destination.""" + label: Optional[str] = None """Human-readable label shown to the user.""" diff --git a/src/kernel/types/auth/managed_auth.py b/src/kernel/types/auth/managed_auth.py index 9db58514..ea5be1c6 100644 --- a/src/kernel/types/auth/managed_auth.py +++ b/src/kernel/types/auth/managed_auth.py @@ -122,9 +122,24 @@ class Choice(BaseModel): ] """Choice type.""" + context: Optional[str] = None + """Context captured for a choice.""" + description: Optional[str] = None """Additional context for the choice.""" + display_text: Optional[str] = None + """Display text captured for a choice.""" + + masked_destination: Optional[str] = None + """Masked phone number or email address shown for an MFA choice.""" + + mfa_type: Optional[Literal["sms", "call", "email", "totp", "push", "password", "passkey", "switch", "other"]] = None + """Semantic MFA method. + + Choice id remains the stable identity of the exact option selected. + """ + observed_selector: Optional[str] = None """Selector for the visible choice, when available.""" @@ -197,6 +212,9 @@ class Field(BaseModel): type: Literal["identifier", "password", "code", "totp_code", "totp_secret", "text"] """Managed-auth field type.""" + hint: Optional[str] = None + """Context shown near the field, including a masked code destination.""" + label: Optional[str] = None """Human-readable label shown to the user.""" From eeb74dff42b450ac48d17082636cd6f383105748 Mon Sep 17 00:00:00 2001 From: "kernel-internal[bot]" <260533166+kernel-internal[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 23:35:49 +0000 Subject: [PATCH 3/3] release: 0.90.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6af24e3d..f2863752 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.89.0" + ".": "0.90.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 92fb5212..f6add314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.90.0](https://github.com/kernel/kernel-python-sdk/compare/v0.89.0...v0.90.0) (2026-08-12) + + +### Features + +* Preserve canonical managed auth input metadata ([3bb6956](https://github.com/kernel/kernel-python-sdk/commit/3bb6956e8021a955cfcbd12fd93d2a3a6b648ed7)) +* Support project selection by ID or name ([67970b2](https://github.com/kernel/kernel-python-sdk/commit/67970b2b8cc0312b81d10e6fec6c3a016c79d347)) + ## [0.89.0](https://github.com/kernel/kernel-python-sdk/compare/v0.88.0...v0.89.0) (2026-08-12) diff --git a/pyproject.toml b/pyproject.toml index 57df3c54..521e38c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.89.0" +version = "0.90.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 e4082c56..8933bc49 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.89.0" # x-release-please-version +__version__ = "0.90.0" # x-release-please-version