diff --git a/.release-please-manifest.json b/.release-please-manifest.json index db00cae..0b8a7c6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.67.0" + ".": "0.68.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index a164009..ab902c2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-51549f813f3002e18c6ca8d850cc0c7932828d511c151e0412c73b6798d19e30.yml -openapi_spec_hash: ee77b293c4bda91c1a32cfdd12b8739e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-c98841235b0ece0591f28f7dd424339b6ef2f3e8f539b95b670ae0da2ef43df4.yml +openapi_spec_hash: c1e9456765f0743a333af297d135d5cf config_hash: 57567e00b41af47cef1b78e51b747aa0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 342bd47..4d57115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.68.0 (2026-06-12) + +Full Changelog: [v0.67.0...v0.68.0](https://github.com/kernel/kernel-python-sdk/compare/v0.67.0...v0.68.0) + +### Features + +* **api:** surface deleted/expired API keys for audit trail (KERNEL-1350) ([6f16159](https://github.com/kernel/kernel-python-sdk/commit/6f16159d28de8129b12dfe2e144e9d1db39ad4ec)) + ## 0.67.0 (2026-06-11) Full Changelog: [v0.66.0...v0.67.0](https://github.com/kernel/kernel-python-sdk/compare/v0.66.0...v0.67.0) diff --git a/api.md b/api.md index 4b24f10..d0bc3c2 100644 --- a/api.md +++ b/api.md @@ -449,7 +449,7 @@ from kernel.types import APIKey, CreatedAPIKey Methods: - client.api_keys.create(\*\*params) -> CreatedAPIKey -- client.api_keys.retrieve(id) -> APIKey +- client.api_keys.retrieve(id, \*\*params) -> APIKey - client.api_keys.update(id, \*\*params) -> APIKey - client.api_keys.list(\*\*params) -> SyncOffsetPagination[APIKey] - client.api_keys.delete(id) -> None diff --git a/pyproject.toml b/pyproject.toml index 035e174..4020062 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.67.0" +version = "0.68.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 7c28de4..b0e3713 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.67.0" # x-release-please-version +__version__ = "0.68.0" # x-release-please-version diff --git a/src/kernel/resources/api_keys.py b/src/kernel/resources/api_keys.py index 93e8015..3801c4f 100644 --- a/src/kernel/resources/api_keys.py +++ b/src/kernel/resources/api_keys.py @@ -7,7 +7,7 @@ import httpx -from ..types import api_key_list_params, api_key_create_params, api_key_update_params +from ..types import api_key_list_params, api_key_create_params, api_key_update_params, api_key_retrieve_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 @@ -99,6 +99,7 @@ def retrieve( self, id: 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. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -112,6 +113,9 @@ def retrieve( masked. Args: + include_deleted: When true, return the API key even if it has been deleted (soft-deleted), for + audit purposes. Defaults to false, which returns 404 for a deleted key. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -125,7 +129,13 @@ def retrieve( return self._get( path_template("/org/api_keys/{id}", id=id), 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( + {"include_deleted": include_deleted}, api_key_retrieve_params.APIKeyRetrieveParams + ), ), cast_to=APIKey, ) @@ -170,6 +180,7 @@ def update( def list( self, *, + include_deleted: bool | Omit = omit, limit: int | Omit = omit, offset: int | Omit = omit, query: str | Omit = omit, @@ -187,6 +198,9 @@ def list( API keys are masked. Args: + include_deleted: When true, include deleted (soft-deleted) API keys in the results for audit + purposes. Defaults to false, which returns only live keys. + limit: Maximum number of results to return offset: Number of results to skip @@ -216,6 +230,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_deleted": include_deleted, "limit": limit, "offset": offset, "query": query, @@ -336,6 +351,7 @@ async def retrieve( self, id: 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. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -349,6 +365,9 @@ async def retrieve( masked. Args: + include_deleted: When true, return the API key even if it has been deleted (soft-deleted), for + audit purposes. Defaults to false, which returns 404 for a deleted key. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -362,7 +381,13 @@ async def retrieve( return await self._get( path_template("/org/api_keys/{id}", id=id), 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( + {"include_deleted": include_deleted}, api_key_retrieve_params.APIKeyRetrieveParams + ), ), cast_to=APIKey, ) @@ -407,6 +432,7 @@ async def update( def list( self, *, + include_deleted: bool | Omit = omit, limit: int | Omit = omit, offset: int | Omit = omit, query: str | Omit = omit, @@ -424,6 +450,9 @@ def list( API keys are masked. Args: + include_deleted: When true, include deleted (soft-deleted) API keys in the results for audit + purposes. Defaults to false, which returns only live keys. + limit: Maximum number of results to return offset: Number of results to skip @@ -453,6 +482,7 @@ def list( timeout=timeout, query=maybe_transform( { + "include_deleted": include_deleted, "limit": limit, "offset": offset, "query": query, diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index 1e9b39a..d091f8d 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -54,6 +54,7 @@ from .deployment_state_event import DeploymentStateEvent as DeploymentStateEvent from .invocation_list_params import InvocationListParams as InvocationListParams from .invocation_state_event import InvocationStateEvent as InvocationStateEvent +from .api_key_retrieve_params import APIKeyRetrieveParams as APIKeyRetrieveParams from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse from .browser_retrieve_params import BrowserRetrieveParams as BrowserRetrieveParams from .browser_update_response import BrowserUpdateResponse as BrowserUpdateResponse diff --git a/src/kernel/types/api_key.py b/src/kernel/types/api_key.py index 6df577f..0f784c6 100644 --- a/src/kernel/types/api_key.py +++ b/src/kernel/types/api_key.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from .._models import BaseModel @@ -28,6 +29,12 @@ class APIKey(BaseModel): created_by: CreatedBy + deleted_at: Optional[datetime] = None + """When the API key was deleted (soft-deleted). + + Null for keys that have not been deleted. + """ + expires_at: Optional[datetime] = None """When the API key expires""" @@ -45,3 +52,11 @@ class APIKey(BaseModel): Null means the key is org-wide or the project name is unavailable. """ + + status: Literal["active", "expired", "deleted"] + """Derived lifecycle status of the API key. + + `active` means usable. `expired` means past its expires_at. `deleted` means it + was deleted (soft-deleted) and can no longer authenticate. Deleted takes + precedence over expired. + """ diff --git a/src/kernel/types/api_key_list_params.py b/src/kernel/types/api_key_list_params.py index 79a9c41..673c5d6 100644 --- a/src/kernel/types/api_key_list_params.py +++ b/src/kernel/types/api_key_list_params.py @@ -8,6 +8,12 @@ class APIKeyListParams(TypedDict, total=False): + include_deleted: bool + """ + When true, include deleted (soft-deleted) API keys in the results for audit + purposes. Defaults to false, which returns only live keys. + """ + limit: int """Maximum number of results to return""" diff --git a/src/kernel/types/api_key_retrieve_params.py b/src/kernel/types/api_key_retrieve_params.py new file mode 100644 index 0000000..e2b9ea3 --- /dev/null +++ b/src/kernel/types/api_key_retrieve_params.py @@ -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 TypedDict + +__all__ = ["APIKeyRetrieveParams"] + + +class APIKeyRetrieveParams(TypedDict, total=False): + include_deleted: bool + """ + When true, return the API key even if it has been deleted (soft-deleted), for + audit purposes. Defaults to false, which returns 404 for a deleted key. + """ diff --git a/tests/api_resources/test_api_keys.py b/tests/api_resources/test_api_keys.py index 9c3abca..3427383 100644 --- a/tests/api_resources/test_api_keys.py +++ b/tests/api_resources/test_api_keys.py @@ -9,7 +9,10 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type -from kernel.types import APIKey, CreatedAPIKey +from kernel.types import ( + APIKey, + CreatedAPIKey, +) from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -66,7 +69,16 @@ def test_streaming_response_create(self, client: Kernel) -> None: @parametrize def test_method_retrieve(self, client: Kernel) -> None: api_key = client.api_keys.retrieve( - "id", + id="id", + ) + assert_matches_type(APIKey, api_key, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve_with_all_params(self, client: Kernel) -> None: + api_key = client.api_keys.retrieve( + id="id", + include_deleted=True, ) assert_matches_type(APIKey, api_key, path=["response"]) @@ -74,7 +86,7 @@ def test_method_retrieve(self, client: Kernel) -> None: @parametrize def test_raw_response_retrieve(self, client: Kernel) -> None: response = client.api_keys.with_raw_response.retrieve( - "id", + id="id", ) assert response.is_closed is True @@ -86,7 +98,7 @@ def test_raw_response_retrieve(self, client: Kernel) -> None: @parametrize def test_streaming_response_retrieve(self, client: Kernel) -> None: with client.api_keys.with_streaming_response.retrieve( - "id", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -101,7 +113,7 @@ def test_streaming_response_retrieve(self, client: Kernel) -> None: def test_path_params_retrieve(self, client: Kernel) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.api_keys.with_raw_response.retrieve( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -160,6 +172,7 @@ def test_method_list(self, client: Kernel) -> None: @parametrize def test_method_list_with_all_params(self, client: Kernel) -> None: api_key = client.api_keys.list( + include_deleted=True, limit=100, offset=0, query="query", @@ -286,7 +299,16 @@ async def test_streaming_response_create(self, async_client: AsyncKernel) -> Non @parametrize async def test_method_retrieve(self, async_client: AsyncKernel) -> None: api_key = await async_client.api_keys.retrieve( - "id", + id="id", + ) + assert_matches_type(APIKey, api_key, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve_with_all_params(self, async_client: AsyncKernel) -> None: + api_key = await async_client.api_keys.retrieve( + id="id", + include_deleted=True, ) assert_matches_type(APIKey, api_key, path=["response"]) @@ -294,7 +316,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.api_keys.with_raw_response.retrieve( - "id", + id="id", ) assert response.is_closed is True @@ -306,7 +328,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.api_keys.with_streaming_response.retrieve( - "id", + id="id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -321,7 +343,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncKernel) -> N 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 ''"): await async_client.api_keys.with_raw_response.retrieve( - "", + id="", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -380,6 +402,7 @@ async def test_method_list(self, async_client: AsyncKernel) -> None: @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None: api_key = await async_client.api_keys.list( + include_deleted=True, limit=100, offset=0, query="query",