diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fd0ccba9..000572ec 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.12" + ".": "0.1.0-alpha.13" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 01c41add..f0d8544e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-c9d64df733f286f09d2203f4e3d820ce57e8d4c629c5e2db4e2bfac91fbc1598.yml -openapi_spec_hash: fa407611fc566d55f403864fbfaa6c23 -config_hash: 7f67c5b95af1e4b39525515240b72275 +configured_endpoints: 6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-19b0d17ba368f32827ee322d15a7f4ff7e1f3bbf66606fad227b3465f8ffc5ab.yml +openapi_spec_hash: 4a3cb766898e8a134ef99fe6c4c87736 +config_hash: 4dfa4d870ce0e23e31ce33ab6a53dd21 diff --git a/CHANGELOG.md b/CHANGELOG.md index f9605e49..49c41f1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.0-alpha.13 (2025-05-20) + +Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/onkernel/kernel-python-sdk/compare/v0.1.0-alpha.12...v0.1.0-alpha.13) + +### Features + +* **api:** update via SDK Studio ([c528c7b](https://github.com/onkernel/kernel-python-sdk/commit/c528c7b45adac371fddfdc2792a435f814b03d67)) + ## 0.1.0-alpha.12 (2025-05-19) Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/onkernel/kernel-python-sdk/compare/v0.1.0-alpha.11...v0.1.0-alpha.12) diff --git a/api.md b/api.md index 63d7b00d..32a9bb03 100644 --- a/api.md +++ b/api.md @@ -1,15 +1,5 @@ # Apps -Types: - -```python -from kernel.types import AppListResponse -``` - -Methods: - -- client.apps.list(\*\*params) -> AppListResponse - ## Deployments Types: diff --git a/pyproject.toml b/pyproject.toml index 3871d47e..4e20f732 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.1.0-alpha.12" +version = "0.1.0-alpha.13" 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 02886717..d48ee69d 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.1.0-alpha.12" # x-release-please-version +__version__ = "0.1.0-alpha.13" # x-release-please-version diff --git a/src/kernel/app_framework.py b/src/kernel/app_framework.py index 158ed9e4..21a3a97c 100644 --- a/src/kernel/app_framework.py +++ b/src/kernel/app_framework.py @@ -1,7 +1,7 @@ import json import inspect import functools -from typing import Any, Dict, List, Union, TypeVar, Callable, Optional +from typing import Any, Dict, List, TypeVar, Callable, Optional from dataclasses import dataclass T = TypeVar("T") @@ -44,53 +44,58 @@ def __init__(self, name: str): # Register this app in the global registry _app_registry.register_app(self) - def action(self, name_or_handler: Optional[Union[str, Callable[..., Any]]] = None) -> Callable[..., Any]: - """Decorator to register an action with the app""" - if name_or_handler is None: - # This is the @app.action() case, which should return the decorator - def decorator(f: Callable[..., Any]) -> Callable[..., Any]: - return self._register_action(f.__name__, f) - return decorator - elif callable(name_or_handler): - # This is the @app.action case (handler passed directly) - return self._register_action(name_or_handler.__name__, name_or_handler) - else: - # This is the @app.action("name") case (name_or_handler is a string) - def decorator(f: Callable[..., Any]) -> Callable[..., Any]: - return self._register_action(name_or_handler, f) # name_or_handler is the name string here - return decorator + def action(self, name: str) -> Callable[..., Any]: + """ + Decorator to register an action with the app + + Usage: + @app.action("action-name") + def my_handler(ctx: KernelContext): + # ... + + @app.action("action-with-payload") + def my_handler(ctx: KernelContext, payload: dict): + # ... + """ + def decorator(handler: Callable[..., Any]) -> Callable[..., Any]: + return self._register_action(name, handler) + return decorator def _register_action(self, name: str, handler: Callable[..., Any]) -> Callable[..., Any]: """Internal method to register an action""" + # Validate handler signature + sig = inspect.signature(handler) + param_count = len(sig.parameters) + + if param_count == 0: + raise TypeError("Action handler must accept at least the context parameter") + elif param_count > 2: + raise TypeError("Action handler can only accept context and payload parameters") + + param_names = list(sig.parameters.keys()) @functools.wraps(handler) def wrapper(*args: Any, **kwargs: Any) -> Any: - # Determine if the original handler accepts context as first argument - sig = inspect.signature(handler) - param_names = list(sig.parameters.keys()) - param_count = len(param_names) - + # Ensure the first argument is the context + if not args or not isinstance(args[0], KernelContext): + raise TypeError("First argument to action handler must be a KernelContext") + + ctx = args[0] + if param_count == 1: - actual_input = None - # The handler only takes input - if len(args) > 0: # Prioritize args if context was implicitly passed - # If context (args[0]) and input (args[1]) were provided, or just input (args[0]) - actual_input = args[1] if len(args) > 1 else args[0] - elif kwargs: - # Attempt to find the single expected kwarg - if param_names: # Should always be true if param_count == 1 - param_name = param_names[0] - if param_name in kwargs: - actual_input = kwargs[param_name] - elif kwargs: # Fallback if name doesn't match but kwargs exist - actual_input = next(iter(kwargs.values())) - elif kwargs: # param_names is empty but kwargs exist (unlikely for param_count==1) - actual_input = next(iter(kwargs.values())) - # If no args/kwargs, actual_input remains None, handler might raise error or accept None - return handler(actual_input) - else: # param_count == 0 or param_count > 1 - # Handler takes context and input (or more), or no args - return handler(*args, **kwargs) + # Handler takes only context + return handler(ctx) + else: # param_count == 2 + # Handler takes context and payload + if len(args) >= 2: + return handler(ctx, args[1]) + else: + # Try to find payload in kwargs + payload_name = param_names[1] + if payload_name in kwargs: + return handler(ctx, kwargs[payload_name]) + else: + raise TypeError(f"Missing required payload parameter '{payload_name}'") action = KernelAction(name=name, handler=wrapper) self.actions[name] = action diff --git a/src/kernel/resources/apps/apps.py b/src/kernel/resources/apps/apps.py index 9a5f6670..848b765b 100644 --- a/src/kernel/resources/apps/apps.py +++ b/src/kernel/resources/apps/apps.py @@ -2,19 +2,8 @@ from __future__ import annotations -import httpx - -from ...types import app_list_params -from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) from .deployments import ( DeploymentsResource, AsyncDeploymentsResource, @@ -31,8 +20,6 @@ InvocationsResourceWithStreamingResponse, AsyncInvocationsResourceWithStreamingResponse, ) -from ..._base_client import make_request_options -from ...types.app_list_response import AppListResponse __all__ = ["AppsResource", "AsyncAppsResource"] @@ -65,54 +52,6 @@ def with_streaming_response(self) -> AppsResourceWithStreamingResponse: """ return AppsResourceWithStreamingResponse(self) - def list( - self, - *, - app_name: str | NotGiven = NOT_GIVEN, - version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> AppListResponse: - """List application versions for the authenticated user. - - Optionally filter by app - name and/or version label. - - Args: - app_name: Filter results by application name. - - version: Filter results by version label. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/apps", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "app_name": app_name, - "version": version, - }, - app_list_params.AppListParams, - ), - ), - cast_to=AppListResponse, - ) - class AsyncAppsResource(AsyncAPIResource): @cached_property @@ -142,63 +81,11 @@ def with_streaming_response(self) -> AsyncAppsResourceWithStreamingResponse: """ return AsyncAppsResourceWithStreamingResponse(self) - async def list( - self, - *, - app_name: str | NotGiven = NOT_GIVEN, - version: str | NotGiven = NOT_GIVEN, - # 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, - ) -> AppListResponse: - """List application versions for the authenticated user. - - Optionally filter by app - name and/or version label. - - Args: - app_name: Filter results by application name. - - version: Filter results by version label. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._get( - "/apps", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "app_name": app_name, - "version": version, - }, - app_list_params.AppListParams, - ), - ), - cast_to=AppListResponse, - ) - class AppsResourceWithRawResponse: def __init__(self, apps: AppsResource) -> None: self._apps = apps - self.list = to_raw_response_wrapper( - apps.list, - ) - @cached_property def deployments(self) -> DeploymentsResourceWithRawResponse: return DeploymentsResourceWithRawResponse(self._apps.deployments) @@ -212,10 +99,6 @@ class AsyncAppsResourceWithRawResponse: def __init__(self, apps: AsyncAppsResource) -> None: self._apps = apps - self.list = async_to_raw_response_wrapper( - apps.list, - ) - @cached_property def deployments(self) -> AsyncDeploymentsResourceWithRawResponse: return AsyncDeploymentsResourceWithRawResponse(self._apps.deployments) @@ -229,10 +112,6 @@ class AppsResourceWithStreamingResponse: def __init__(self, apps: AppsResource) -> None: self._apps = apps - self.list = to_streamed_response_wrapper( - apps.list, - ) - @cached_property def deployments(self) -> DeploymentsResourceWithStreamingResponse: return DeploymentsResourceWithStreamingResponse(self._apps.deployments) @@ -246,10 +125,6 @@ class AsyncAppsResourceWithStreamingResponse: def __init__(self, apps: AsyncAppsResource) -> None: self._apps = apps - self.list = async_to_streamed_response_wrapper( - apps.list, - ) - @cached_property def deployments(self) -> AsyncDeploymentsResourceWithStreamingResponse: return AsyncDeploymentsResourceWithStreamingResponse(self._apps.deployments) diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index e7c3cecd..282c8899 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -2,8 +2,6 @@ from __future__ import annotations -from .app_list_params import AppListParams as AppListParams -from .app_list_response import AppListResponse as AppListResponse from .browser_create_params import BrowserCreateParams as BrowserCreateParams from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse from .browser_retrieve_response import BrowserRetrieveResponse as BrowserRetrieveResponse diff --git a/src/kernel/types/app_list_params.py b/src/kernel/types/app_list_params.py deleted file mode 100644 index d4506a3e..00000000 --- a/src/kernel/types/app_list_params.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import TypedDict - -__all__ = ["AppListParams"] - - -class AppListParams(TypedDict, total=False): - app_name: str - """Filter results by application name.""" - - version: str - """Filter results by version label.""" diff --git a/src/kernel/types/app_list_response.py b/src/kernel/types/app_list_response.py deleted file mode 100644 index 8a6f6218..00000000 --- a/src/kernel/types/app_list_response.py +++ /dev/null @@ -1,28 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional -from typing_extensions import TypeAlias - -from .._models import BaseModel - -__all__ = ["AppListResponse", "AppListResponseItem"] - - -class AppListResponseItem(BaseModel): - id: str - """Unique identifier for the app version""" - - app_name: str - """Name of the application""" - - region: str - """Deployment region code""" - - version: str - """Version label for the application""" - - env_vars: Optional[Dict[str, str]] = None - """Environment variables configured for this app version""" - - -AppListResponse: TypeAlias = List[AppListResponseItem] diff --git a/tests/api_resources/test_apps.py b/tests/api_resources/test_apps.py deleted file mode 100644 index b902576a..00000000 --- a/tests/api_resources/test_apps.py +++ /dev/null @@ -1,96 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from kernel import Kernel, AsyncKernel -from tests.utils import assert_matches_type -from kernel.types import AppListResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestApps: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_list(self, client: Kernel) -> None: - app = client.apps.list() - assert_matches_type(AppListResponse, app, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_list_with_all_params(self, client: Kernel) -> None: - app = client.apps.list( - app_name="app_name", - version="version", - ) - assert_matches_type(AppListResponse, app, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_list(self, client: Kernel) -> None: - response = client.apps.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - app = response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_list(self, client: Kernel) -> None: - with client.apps.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - app = response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncApps: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - async def test_method_list(self, async_client: AsyncKernel) -> None: - app = await async_client.apps.list() - assert_matches_type(AppListResponse, app, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None: - app = await async_client.apps.list( - app_name="app_name", - version="version", - ) - assert_matches_type(AppListResponse, app, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_list(self, async_client: AsyncKernel) -> None: - response = await async_client.apps.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - app = await response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: - async with async_client.apps.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - app = await response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) - - assert cast(Any, response.is_closed) is True