From 53e3b18133c23d88eb66caf9b9b4b5ad463df1d5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:53:56 +0000 Subject: [PATCH 01/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index c2d3476..406588c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 107 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-e0b7b296f7b0cac79675a790974f3ad90eb46833027467ee97c3ed21675628b8.yml openapi_spec_hash: 5e60faefb18dd2fca721f14252ab907a -config_hash: dbb3c94874836d520e64fc8ffda4f0d3 +config_hash: a411a6a5bb4519b00f84eef745a194b3 From 46bea8a4493318b0b37904119e057cb7b4e50550 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 08:53:20 +0000 Subject: [PATCH 02/21] feat: provide more context for policy schema --- .stats.yml | 8 +- README.md | 9 - api.md | 2 - .../resources/zones/policies/versions.py | 4 + .../resources/zones/policy_schemas.py | 34 ++- .../zones/policy_sets/policy_sets.py | 16 ++ .../resources/zones/policy_sets/versions.py | 4 + src/keycardai_api/resources/zones/zones.py | 270 +++++++----------- src/keycardai_api/types/__init__.py | 6 - ...one_list_session_resource_access_params.py | 39 --- ...e_list_session_resource_access_response.py | 53 ---- .../types/zones/policies/policy_version.py | 1 + .../zones/policies/version_create_params.py | 1 + src/keycardai_api/types/zones/policy.py | 5 + src/keycardai_api/types/zones/policy_set.py | 12 + .../types/zones/policy_set_create_params.py | 7 + .../zones/policy_sets/policy_set_version.py | 4 + .../policy_sets/version_create_params.py | 1 + .../types/zones/schema_version.py | 16 ++ .../zones/schema_version_with_zone_info.py | 13 + tests/api_resources/test_zones.py | 117 -------- 21 files changed, 216 insertions(+), 406 deletions(-) delete mode 100644 src/keycardai_api/types/zone_list_session_resource_access_params.py delete mode 100644 src/keycardai_api/types/zone_list_session_resource_access_response.py diff --git a/.stats.yml b/.stats.yml index 406588c..0f71c04 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 107 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-e0b7b296f7b0cac79675a790974f3ad90eb46833027467ee97c3ed21675628b8.yml -openapi_spec_hash: 5e60faefb18dd2fca721f14252ab907a -config_hash: a411a6a5bb4519b00f84eef745a194b3 +configured_endpoints: 106 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-2052af02ac981a44897f047d65dcc9e7a7643c8b2168dd73969ef94ec3d56948.yml +openapi_spec_hash: ed47ef3c3f1aed86c2cb1ff009d60483 +config_hash: 8fdc6a9c1185417459f79052b1222ff0 diff --git a/README.md b/README.md index fabc50f..cf222f1 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,6 @@ and offers both synchronous and asynchronous clients powered by [httpx](https:// It is generated with [Stainless](https://www.stainless.com/). -## MCP Server - -Use the Keycard API MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. - -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40keycardai%2Fapi-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBrZXljYXJkYWkvYXBpLW1jcCJdLCJlbnYiOnsiS0VZQ0FSRF9BUElfQVBJX0tFWSI6Ik15IEFQSSBLZXkiLCJLRVlDQVJEX0FQSV9DTElFTlRfSUQiOiJNeSBDbGllbnQgSUQiLCJLRVlDQVJEX0FQSV9DTElFTlRfU0VDUkVUIjoiTXkgQ2xpZW50IFNlY3JldCJ9fQ) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40keycardai%2Fapi-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40keycardai%2Fapi-mcp%22%5D%2C%22env%22%3A%7B%22KEYCARD_API_API_KEY%22%3A%22My%20API%20Key%22%2C%22KEYCARD_API_CLIENT_ID%22%3A%22My%20Client%20ID%22%2C%22KEYCARD_API_CLIENT_SECRET%22%3A%22My%20Client%20Secret%22%7D%7D) - -> Note: You may need to set environment variables in your MCP client. - ## Documentation The REST API documentation can be found on [docs.keycard.ai](https://docs.keycard.ai). The full API of this library can be found in [api.md](api.md). diff --git a/api.md b/api.md index 2fe229e..de50220 100644 --- a/api.md +++ b/api.md @@ -8,7 +8,6 @@ from keycardai_api.types import ( PageInfoPagination, Zone, ZoneListResponse, - ZoneListSessionResourceAccessResponse, ) ``` @@ -19,7 +18,6 @@ Methods: - client.zones.update(zone_id, \*\*params) -> Zone - client.zones.list(\*\*params) -> ZoneListResponse - client.zones.delete(zone_id) -> None -- client.zones.list_session_resource_access(zone_id, \*\*params) -> ZoneListSessionResourceAccessResponse ## Applications diff --git a/src/keycardai_api/resources/zones/policies/versions.py b/src/keycardai_api/resources/zones/policies/versions.py index f343374..950f2d0 100644 --- a/src/keycardai_api/resources/zones/policies/versions.py +++ b/src/keycardai_api/resources/zones/policies/versions.py @@ -68,6 +68,8 @@ def create( Create a new immutable policy version Args: + schema_version: Schema version to validate this policy against. Must not be archived. + cedar_json: Cedar policy in JSON representation. Mutually exclusive with cedar_raw. cedar_raw: Cedar policy in human-readable Cedar syntax. Mutually exclusive with cedar_json. @@ -348,6 +350,8 @@ async def create( Create a new immutable policy version Args: + schema_version: Schema version to validate this policy against. Must not be archived. + cedar_json: Cedar policy in JSON representation. Mutually exclusive with cedar_raw. cedar_raw: Cedar policy in human-readable Cedar syntax. Mutually exclusive with cedar_json. diff --git a/src/keycardai_api/resources/zones/policy_schemas.py b/src/keycardai_api/resources/zones/policy_schemas.py index f04688f..66c7f2e 100644 --- a/src/keycardai_api/resources/zones/policy_schemas.py +++ b/src/keycardai_api/resources/zones/policy_schemas.py @@ -26,7 +26,22 @@ class PolicySchemasResource(SyncAPIResource): - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ @cached_property def with_raw_response(self) -> PolicySchemasResourceWithRawResponse: @@ -243,7 +258,22 @@ def set_default( class AsyncPolicySchemasResource(AsyncAPIResource): - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ @cached_property def with_raw_response(self) -> AsyncPolicySchemasResourceWithRawResponse: diff --git a/src/keycardai_api/resources/zones/policy_sets/policy_sets.py b/src/keycardai_api/resources/zones/policy_sets/policy_sets.py index af4531a..7ab3357 100644 --- a/src/keycardai_api/resources/zones/policy_sets/policy_sets.py +++ b/src/keycardai_api/resources/zones/policy_sets/policy_sets.py @@ -81,6 +81,14 @@ def create( version. Args: + scope_type: + The scope at which this policy set applies: + + - `"zone"` — applies to all requests in the zone. + - `"resource"` — scoped to a specific resource. + - `"user"` — scoped to a specific user. + - `"session"` — scoped to a specific session. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -391,6 +399,14 @@ async def create( version. Args: + scope_type: + The scope at which this policy set applies: + + - `"zone"` — applies to all requests in the zone. + - `"resource"` — scoped to a specific resource. + - `"user"` — scoped to a specific user. + - `"session"` — scoped to a specific session. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/keycardai_api/resources/zones/policy_sets/versions.py b/src/keycardai_api/resources/zones/policy_sets/versions.py index 8007874..c07fb7a 100644 --- a/src/keycardai_api/resources/zones/policy_sets/versions.py +++ b/src/keycardai_api/resources/zones/policy_sets/versions.py @@ -74,6 +74,8 @@ def create( Validates the manifest, computes SHA, and creates an immutable version snapshot. Args: + schema_version: Schema version to pin to this policy set version. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -478,6 +480,8 @@ async def create( Validates the manifest, computes SHA, and creates an immutable version snapshot. Args: + schema_version: Schema version to pin to this policy set version. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/keycardai_api/resources/zones/zones.py b/src/keycardai_api/resources/zones/zones.py index 02a12ff..6598977 100644 --- a/src/keycardai_api/resources/zones/zones.py +++ b/src/keycardai_api/resources/zones/zones.py @@ -20,7 +20,6 @@ zone_create_params, zone_update_params, zone_retrieve_params, - zone_list_session_resource_access_params, ) from .members import ( MembersResource, @@ -132,7 +131,6 @@ ) from ...types.zone_list_response import ZoneListResponse from ...types.encryption_key_aws_kms_config_param import EncryptionKeyAwsKmsConfigParam -from ...types.zone_list_session_resource_access_response import ZoneListSessionResourceAccessResponse __all__ = ["ZonesResource", "AsyncZonesResource"] @@ -180,7 +178,22 @@ def secrets(self) -> SecretsResource: @cached_property def policy_schemas(self) -> PolicySchemasResource: - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ return PolicySchemasResource(self._client) @cached_property @@ -483,83 +496,6 @@ def delete( cast_to=NoneType, ) - def list_session_resource_access( - self, - zone_id: str, - *, - after: str | Omit = omit, - before: str | Omit = omit, - expand: Union[Literal["total_count"], List[Literal["total_count"]]] | Omit = omit, - limit: int | Omit = omit, - resource_id: str | Omit = omit, - rollup_children: bool | Omit = omit, - session_id: str | Omit = omit, - user_id: str | 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, - ) -> ZoneListSessionResourceAccessResponse: - """Returns aggregated access records per session-resource pair. - - By default - (rollup_children=true), includes access from descendant sessions. Set - rollup_children=false to return only direct session access. At least one of - user_id, session_id, or resource_id must be provided. - - Args: - after: Cursor for forward pagination - - before: Cursor for backward pagination - - limit: Maximum number of items to return - - resource_id: Filter by resource ID - - rollup_children: Include resource access from descendant sessions. When true (default), - aggregates access from the session and all its descendants. When false, returns - only direct access for the session. - - session_id: Filter by session ID - - user_id: Filter by user ID - - 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 - """ - if not zone_id: - raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") - return self._get( - f"/zones/{zone_id}/session-resource-access", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "after": after, - "before": before, - "expand": expand, - "limit": limit, - "resource_id": resource_id, - "rollup_children": rollup_children, - "session_id": session_id, - "user_id": user_id, - }, - zone_list_session_resource_access_params.ZoneListSessionResourceAccessParams, - ), - ), - cast_to=ZoneListSessionResourceAccessResponse, - ) - class AsyncZonesResource(AsyncAPIResource): @cached_property @@ -604,7 +540,22 @@ def secrets(self) -> AsyncSecretsResource: @cached_property def policy_schemas(self) -> AsyncPolicySchemasResource: - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ return AsyncPolicySchemasResource(self._client) @cached_property @@ -907,83 +858,6 @@ async def delete( cast_to=NoneType, ) - async def list_session_resource_access( - self, - zone_id: str, - *, - after: str | Omit = omit, - before: str | Omit = omit, - expand: Union[Literal["total_count"], List[Literal["total_count"]]] | Omit = omit, - limit: int | Omit = omit, - resource_id: str | Omit = omit, - rollup_children: bool | Omit = omit, - session_id: str | Omit = omit, - user_id: str | 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, - ) -> ZoneListSessionResourceAccessResponse: - """Returns aggregated access records per session-resource pair. - - By default - (rollup_children=true), includes access from descendant sessions. Set - rollup_children=false to return only direct session access. At least one of - user_id, session_id, or resource_id must be provided. - - Args: - after: Cursor for forward pagination - - before: Cursor for backward pagination - - limit: Maximum number of items to return - - resource_id: Filter by resource ID - - rollup_children: Include resource access from descendant sessions. When true (default), - aggregates access from the session and all its descendants. When false, returns - only direct access for the session. - - session_id: Filter by session ID - - user_id: Filter by user ID - - 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 - """ - if not zone_id: - raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") - return await self._get( - f"/zones/{zone_id}/session-resource-access", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "after": after, - "before": before, - "expand": expand, - "limit": limit, - "resource_id": resource_id, - "rollup_children": rollup_children, - "session_id": session_id, - "user_id": user_id, - }, - zone_list_session_resource_access_params.ZoneListSessionResourceAccessParams, - ), - ), - cast_to=ZoneListSessionResourceAccessResponse, - ) - class ZonesResourceWithRawResponse: def __init__(self, zones: ZonesResource) -> None: @@ -1004,9 +878,6 @@ def __init__(self, zones: ZonesResource) -> None: self.delete = to_raw_response_wrapper( zones.delete, ) - self.list_session_resource_access = to_raw_response_wrapper( - zones.list_session_resource_access, - ) @cached_property def applications(self) -> ApplicationsResourceWithRawResponse: @@ -1050,7 +921,22 @@ def secrets(self) -> SecretsResourceWithRawResponse: @cached_property def policy_schemas(self) -> PolicySchemasResourceWithRawResponse: - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ return PolicySchemasResourceWithRawResponse(self._zones.policy_schemas) @cached_property @@ -1083,9 +969,6 @@ def __init__(self, zones: AsyncZonesResource) -> None: self.delete = async_to_raw_response_wrapper( zones.delete, ) - self.list_session_resource_access = async_to_raw_response_wrapper( - zones.list_session_resource_access, - ) @cached_property def applications(self) -> AsyncApplicationsResourceWithRawResponse: @@ -1129,7 +1012,22 @@ def secrets(self) -> AsyncSecretsResourceWithRawResponse: @cached_property def policy_schemas(self) -> AsyncPolicySchemasResourceWithRawResponse: - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ return AsyncPolicySchemasResourceWithRawResponse(self._zones.policy_schemas) @cached_property @@ -1162,9 +1060,6 @@ def __init__(self, zones: ZonesResource) -> None: self.delete = to_streamed_response_wrapper( zones.delete, ) - self.list_session_resource_access = to_streamed_response_wrapper( - zones.list_session_resource_access, - ) @cached_property def applications(self) -> ApplicationsResourceWithStreamingResponse: @@ -1208,7 +1103,22 @@ def secrets(self) -> SecretsResourceWithStreamingResponse: @cached_property def policy_schemas(self) -> PolicySchemasResourceWithStreamingResponse: - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ return PolicySchemasResourceWithStreamingResponse(self._zones.policy_schemas) @cached_property @@ -1241,9 +1151,6 @@ def __init__(self, zones: AsyncZonesResource) -> None: self.delete = async_to_streamed_response_wrapper( zones.delete, ) - self.list_session_resource_access = async_to_streamed_response_wrapper( - zones.list_session_resource_access, - ) @cached_property def applications(self) -> AsyncApplicationsResourceWithStreamingResponse: @@ -1287,7 +1194,22 @@ def secrets(self) -> AsyncSecretsResourceWithStreamingResponse: @cached_property def policy_schemas(self) -> AsyncPolicySchemasResourceWithStreamingResponse: - """Zone-scoped Cedar schema management""" + """Zone-scoped Cedar schema management. + + The Cedar schema defines the entity model used for authorization decisions. + Key entity types and their attributes: + + - **Keycard::User** — `email` (String), `groups` (Set of String) + - **Keycard::Application** — `registration_method` (RegistrationMethod entity), `credential_type` (CredentialType entity) + - **Keycard::RegistrationMethod** — enum entity: `"managed"`, `"dcr"` + - **Keycard::CredentialType** — enum entity: `"token"`, `"password"`, `"public-key"`, `"url"`, `"public"` + - **Keycard::Resource** — `id` (String), `name` (String), `scopes` (Set of String) + - **Keycard::Claims** — `email` (String), `groups` (Set of String), plus arbitrary additional fields + + Enum-like attributes use Cedar enum entity types (schema version `2026-03-16`+). + In policies, reference values as `RegistrationMethod::"managed"` or `CredentialType::"token"`. + See the Credentials API spec for the full entity model reference. + """ return AsyncPolicySchemasResourceWithStreamingResponse(self._zones.policy_schemas) @cached_property diff --git a/src/keycardai_api/types/__init__.py b/src/keycardai_api/types/__init__.py index 3d22585..d89e8f9 100644 --- a/src/keycardai_api/types/__init__.py +++ b/src/keycardai_api/types/__init__.py @@ -28,9 +28,3 @@ from .organization_list_identities_response import ( OrganizationListIdentitiesResponse as OrganizationListIdentitiesResponse, ) -from .zone_list_session_resource_access_params import ( - ZoneListSessionResourceAccessParams as ZoneListSessionResourceAccessParams, -) -from .zone_list_session_resource_access_response import ( - ZoneListSessionResourceAccessResponse as ZoneListSessionResourceAccessResponse, -) diff --git a/src/keycardai_api/types/zone_list_session_resource_access_params.py b/src/keycardai_api/types/zone_list_session_resource_access_params.py deleted file mode 100644 index 2139950..0000000 --- a/src/keycardai_api/types/zone_list_session_resource_access_params.py +++ /dev/null @@ -1,39 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Union -from typing_extensions import Literal, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["ZoneListSessionResourceAccessParams"] - - -class ZoneListSessionResourceAccessParams(TypedDict, total=False): - after: str - """Cursor for forward pagination""" - - before: str - """Cursor for backward pagination""" - - expand: Annotated[Union[Literal["total_count"], List[Literal["total_count"]]], PropertyInfo(alias="expand[]")] - - limit: int - """Maximum number of items to return""" - - resource_id: str - """Filter by resource ID""" - - rollup_children: bool - """Include resource access from descendant sessions. - - When true (default), aggregates access from the session and all its descendants. - When false, returns only direct access for the session. - """ - - session_id: str - """Filter by session ID""" - - user_id: str - """Filter by user ID""" diff --git a/src/keycardai_api/types/zone_list_session_resource_access_response.py b/src/keycardai_api/types/zone_list_session_resource_access_response.py deleted file mode 100644 index dd0d77f..0000000 --- a/src/keycardai_api/types/zone_list_session_resource_access_response.py +++ /dev/null @@ -1,53 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional -from datetime import datetime - -from .._models import BaseModel - -__all__ = ["ZoneListSessionResourceAccessResponse", "Item", "Pagination"] - - -class Item(BaseModel): - """Aggregated record of session-resource access events""" - - first_accessed_at: datetime - """When access first occurred""" - - last_accessed_at: datetime - """When access most recently occurred""" - - organization_id: str - """Organization ID""" - - resource_id: str - """Resource ID""" - - session_id: str - """Session ID""" - - total_access_count: int - """Total number of access events for this session-resource pair""" - - -class Pagination(BaseModel): - """Cursor-based pagination metadata""" - - after_cursor: Optional[str] = None - """An opaque cursor used for paginating through a list of results""" - - before_cursor: Optional[str] = None - """An opaque cursor used for paginating through a list of results""" - - total_count: Optional[int] = None - """Total number of items matching the query. - - Only included when expand[]=total_count is requested. - """ - - -class ZoneListSessionResourceAccessResponse(BaseModel): - items: List[Item] - - pagination: Pagination - """Cursor-based pagination metadata""" diff --git a/src/keycardai_api/types/zones/policies/policy_version.py b/src/keycardai_api/types/zones/policies/policy_version.py index e16b24e..9306ce5 100644 --- a/src/keycardai_api/types/zones/policies/policy_version.py +++ b/src/keycardai_api/types/zones/policies/policy_version.py @@ -18,6 +18,7 @@ class PolicyVersion(BaseModel): policy_id: str schema_version: str + """Schema version this policy was validated against when created.""" sha: str """Hex-encoded content hash""" diff --git a/src/keycardai_api/types/zones/policies/version_create_params.py b/src/keycardai_api/types/zones/policies/version_create_params.py index 3c36f37..96d245c 100644 --- a/src/keycardai_api/types/zones/policies/version_create_params.py +++ b/src/keycardai_api/types/zones/policies/version_create_params.py @@ -14,6 +14,7 @@ class VersionCreateParams(TypedDict, total=False): zone_id: Required[str] schema_version: Required[str] + """Schema version to validate this policy against. Must not be archived.""" cedar_json: Optional[object] """Cedar policy in JSON representation. Mutually exclusive with cedar_raw.""" diff --git a/src/keycardai_api/types/zones/policy.py b/src/keycardai_api/types/zones/policy.py index 6119e97..a542719 100644 --- a/src/keycardai_api/types/zones/policy.py +++ b/src/keycardai_api/types/zones/policy.py @@ -19,6 +19,11 @@ class Policy(BaseModel): name: str owner_type: Literal["platform", "customer"] + """Who manages this policy: + + - `"platform"` — managed by the Keycard platform (system policies). + - `"customer"` — managed by the tenant (custom policies). + """ updated_at: datetime diff --git a/src/keycardai_api/types/zones/policy_set.py b/src/keycardai_api/types/zones/policy_set.py index a8cd4b6..5436db8 100644 --- a/src/keycardai_api/types/zones/policy_set.py +++ b/src/keycardai_api/types/zones/policy_set.py @@ -19,8 +19,20 @@ class PolicySet(BaseModel): name: str owner_type: Literal["platform", "customer"] + """Who manages this policy set: + + - `"platform"` — managed by the Keycard platform (system policies). + - `"customer"` — managed by the tenant (custom policies). + """ scope_type: Literal["zone", "resource", "user", "session"] + """The scope at which this policy set applies: + + - `"zone"` — applies to all requests in the zone. + - `"resource"` — scoped to a specific resource. + - `"user"` — scoped to a specific user. + - `"session"` — scoped to a specific session. + """ updated_at: datetime diff --git a/src/keycardai_api/types/zones/policy_set_create_params.py b/src/keycardai_api/types/zones/policy_set_create_params.py index b7b9996..17a1c63 100644 --- a/src/keycardai_api/types/zones/policy_set_create_params.py +++ b/src/keycardai_api/types/zones/policy_set_create_params.py @@ -13,6 +13,13 @@ class PolicySetCreateParams(TypedDict, total=False): name: Required[str] scope_type: Literal["zone", "resource", "user", "session"] + """The scope at which this policy set applies: + + - `"zone"` — applies to all requests in the zone. + - `"resource"` — scoped to a specific resource. + - `"user"` — scoped to a specific user. + - `"session"` — scoped to a specific session. + """ x_api_version: Annotated[str, PropertyInfo(alias="X-API-Version")] diff --git a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py index d04ec8a..e0cfc71 100644 --- a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py +++ b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py @@ -25,6 +25,10 @@ class PolicySetVersion(BaseModel): policy_set_id: str schema_version: str + """Schema version pinned to this policy set version. + + Determines the Cedar schema used for evaluation when activated. + """ version: int diff --git a/src/keycardai_api/types/zones/policy_sets/version_create_params.py b/src/keycardai_api/types/zones/policy_sets/version_create_params.py index d5c2bc8..b381343 100644 --- a/src/keycardai_api/types/zones/policy_sets/version_create_params.py +++ b/src/keycardai_api/types/zones/policy_sets/version_create_params.py @@ -16,6 +16,7 @@ class VersionCreateParams(TypedDict, total=False): manifest: Required[PolicySetManifestParam] schema_version: Required[str] + """Schema version to pin to this policy set version.""" x_api_version: Annotated[str, PropertyInfo(alias="X-API-Version")] diff --git a/src/keycardai_api/types/zones/schema_version.py b/src/keycardai_api/types/zones/schema_version.py index f960778..3b70265 100644 --- a/src/keycardai_api/types/zones/schema_version.py +++ b/src/keycardai_api/types/zones/schema_version.py @@ -10,9 +10,25 @@ class SchemaVersion(BaseModel): + """ + A versioned Cedar schema that defines the entity model, actions, and + context shape used for policy evaluation. The schema contains the valid + entity types (User, Application, Resource), their attributes, and the + allowed attribute values. See the Credentials API spec for a full + reference of entity attributes and valid values. + """ + created_at: datetime status: Literal["active", "deprecated", "archived"] + """Controls what can be done with this schema version: + + - `"active"` - new policy versions can be created and validated against it. + - `"deprecated"` - superseded by a newer version but still accepts new policy + versions. + - `"archived"` - closed to new policy versions. Existing policy set versions + pinned to this schema still evaluate normally. + """ updated_at: datetime diff --git a/src/keycardai_api/types/zones/schema_version_with_zone_info.py b/src/keycardai_api/types/zones/schema_version_with_zone_info.py index a0ec53b..b50f814 100644 --- a/src/keycardai_api/types/zones/schema_version_with_zone_info.py +++ b/src/keycardai_api/types/zones/schema_version_with_zone_info.py @@ -6,4 +6,17 @@ class SchemaVersionWithZoneInfo(SchemaVersion): + """ + A versioned Cedar schema that defines the entity model, actions, and + context shape used for policy evaluation. The schema contains the valid + entity types (User, Application, Resource), their attributes, and the + allowed attribute values. See the Credentials API spec for a full + reference of entity attributes and valid values. + """ + is_default: bool + """Whether this is the zone's default schema. + + Clients use this to pre-select which schema to write policies against. Has no + effect on evaluation. + """ diff --git a/tests/api_resources/test_zones.py b/tests/api_resources/test_zones.py index a895618..e9a1036 100644 --- a/tests/api_resources/test_zones.py +++ b/tests/api_resources/test_zones.py @@ -12,7 +12,6 @@ from keycardai_api.types import ( Zone, ZoneListResponse, - ZoneListSessionResourceAccessResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -277,64 +276,6 @@ def test_path_params_delete(self, client: KeycardAPI) -> None: "", ) - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list_session_resource_access(self, client: KeycardAPI) -> None: - zone = client.zones.list_session_resource_access( - zone_id="zoneId", - ) - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_list_session_resource_access_with_all_params(self, client: KeycardAPI) -> None: - zone = client.zones.list_session_resource_access( - zone_id="zoneId", - after="x", - before="x", - expand="total_count", - limit=1, - resource_id="resource_id", - rollup_children=True, - session_id="session_id", - user_id="user_id", - ) - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_list_session_resource_access(self, client: KeycardAPI) -> None: - response = client.zones.with_raw_response.list_session_resource_access( - zone_id="zoneId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - zone = response.parse() - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_list_session_resource_access(self, client: KeycardAPI) -> None: - with client.zones.with_streaming_response.list_session_resource_access( - zone_id="zoneId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - zone = response.parse() - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_path_params_list_session_resource_access(self, client: KeycardAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `zone_id` but received ''"): - client.zones.with_raw_response.list_session_resource_access( - zone_id="", - ) - class TestAsyncZones: parametrize = pytest.mark.parametrize( @@ -596,61 +537,3 @@ async def test_path_params_delete(self, async_client: AsyncKeycardAPI) -> None: await async_client.zones.with_raw_response.delete( "", ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list_session_resource_access(self, async_client: AsyncKeycardAPI) -> None: - zone = await async_client.zones.list_session_resource_access( - zone_id="zoneId", - ) - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_list_session_resource_access_with_all_params(self, async_client: AsyncKeycardAPI) -> None: - zone = await async_client.zones.list_session_resource_access( - zone_id="zoneId", - after="x", - before="x", - expand="total_count", - limit=1, - resource_id="resource_id", - rollup_children=True, - session_id="session_id", - user_id="user_id", - ) - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_list_session_resource_access(self, async_client: AsyncKeycardAPI) -> None: - response = await async_client.zones.with_raw_response.list_session_resource_access( - zone_id="zoneId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - zone = await response.parse() - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_list_session_resource_access(self, async_client: AsyncKeycardAPI) -> None: - async with async_client.zones.with_streaming_response.list_session_resource_access( - zone_id="zoneId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - zone = await response.parse() - assert_matches_type(ZoneListSessionResourceAccessResponse, zone, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_path_params_list_session_resource_access(self, async_client: AsyncKeycardAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `zone_id` but received ''"): - await async_client.zones.with_raw_response.list_session_resource_access( - zone_id="", - ) From 6993e56d5e9a79713471a0b2b33920c1e2da77d9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:36:04 +0000 Subject: [PATCH 03/21] fix: sanitize endpoint path params --- src/keycardai_api/_utils/__init__.py | 1 + src/keycardai_api/_utils/_path.py | 127 ++++++++++++++++++ src/keycardai_api/resources/invitations.py | 10 +- .../resources/organizations/invitations.py | 22 ++- .../resources/organizations/organizations.py | 22 +-- .../service_accounts/credentials.py | 68 ++++++++-- .../service_accounts/service_accounts.py | 46 +++++-- .../resources/organizations/sso_connection.py | 18 +-- .../resources/organizations/users.py | 30 +++-- .../zones/application_credentials.py | 22 +-- .../zones/applications/applications.py | 30 ++--- .../zones/applications/dependencies.py | 48 +++++-- .../resources/zones/delegated_grants.py | 18 +-- src/keycardai_api/resources/zones/members.py | 46 +++++-- .../resources/zones/policies/policies.py | 22 +-- .../resources/zones/policies/versions.py | 38 ++++-- .../resources/zones/policy_schemas.py | 14 +- .../zones/policy_sets/policy_sets.py | 22 +-- .../resources/zones/policy_sets/versions.py | 74 ++++++++-- .../resources/zones/providers.py | 22 +-- .../resources/zones/resources.py | 22 +-- src/keycardai_api/resources/zones/secrets.py | 22 +-- src/keycardai_api/resources/zones/sessions.py | 18 +-- .../resources/zones/user_agents.py | 10 +- src/keycardai_api/resources/zones/users.py | 10 +- src/keycardai_api/resources/zones/zones.py | 14 +- tests/test_utils/test_path.py | 89 ++++++++++++ 27 files changed, 657 insertions(+), 228 deletions(-) create mode 100644 src/keycardai_api/_utils/_path.py create mode 100644 tests/test_utils/test_path.py diff --git a/src/keycardai_api/_utils/__init__.py b/src/keycardai_api/_utils/__init__.py index dc64e29..10cb66d 100644 --- a/src/keycardai_api/_utils/__init__.py +++ b/src/keycardai_api/_utils/__init__.py @@ -1,3 +1,4 @@ +from ._path import path_template as path_template from ._sync import asyncify as asyncify from ._proxy import LazyProxy as LazyProxy from ._utils import ( diff --git a/src/keycardai_api/_utils/_path.py b/src/keycardai_api/_utils/_path.py new file mode 100644 index 0000000..4d6e1e4 --- /dev/null +++ b/src/keycardai_api/_utils/_path.py @@ -0,0 +1,127 @@ +from __future__ import annotations + +import re +from typing import ( + Any, + Mapping, + Callable, +) +from urllib.parse import quote + +# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E). +_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$") + +_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}") + + +def _quote_path_segment_part(value: str) -> str: + """Percent-encode `value` for use in a URI path segment. + + Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 + """ + # quote() already treats unreserved characters (letters, digits, and -._~) + # as safe, so we only need to add sub-delims, ':', and '@'. + # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted. + return quote(value, safe="!$&'()*+,;=:@") + + +def _quote_query_part(value: str) -> str: + """Percent-encode `value` for use in a URI query string. + + Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.4 + """ + return quote(value, safe="!$'()*+,;:@/?") + + +def _quote_fragment_part(value: str) -> str: + """Percent-encode `value` for use in a URI fragment. + + Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe. + https://datatracker.ietf.org/doc/html/rfc3986#section-3.5 + """ + return quote(value, safe="!$&'()*+,;=:@/?") + + +def _interpolate( + template: str, + values: Mapping[str, Any], + quoter: Callable[[str], str], +) -> str: + """Replace {name} placeholders in `template`, quoting each value with `quoter`. + + Placeholder names are looked up in `values`. + + Raises: + KeyError: If a placeholder is not found in `values`. + """ + # re.split with a capturing group returns alternating + # [text, name, text, name, ..., text] elements. + parts = _PLACEHOLDER_RE.split(template) + + for i in range(1, len(parts), 2): + name = parts[i] + if name not in values: + raise KeyError(f"a value for placeholder {{{name}}} was not provided") + val = values[name] + if val is None: + parts[i] = "null" + elif isinstance(val, bool): + parts[i] = "true" if val else "false" + else: + parts[i] = quoter(str(values[name])) + + return "".join(parts) + + +def path_template(template: str, /, **kwargs: Any) -> str: + """Interpolate {name} placeholders in `template` from keyword arguments. + + Args: + template: The template string containing {name} placeholders. + **kwargs: Keyword arguments to interpolate into the template. + + Returns: + The template with placeholders interpolated and percent-encoded. + + Safe characters for percent-encoding are dependent on the URI component. + Placeholders in path and fragment portions are percent-encoded where the `segment` + and `fragment` sets from RFC 3986 respectively are considered safe. + Placeholders in the query portion are percent-encoded where the `query` set from + RFC 3986 §3.3 is considered safe except for = and & characters. + + Raises: + KeyError: If a placeholder is not found in `kwargs`. + ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments). + """ + # Split the template into path, query, and fragment portions. + fragment_template: str | None = None + query_template: str | None = None + + rest = template + if "#" in rest: + rest, fragment_template = rest.split("#", 1) + if "?" in rest: + rest, query_template = rest.split("?", 1) + path_template = rest + + # Interpolate each portion with the appropriate quoting rules. + path_result = _interpolate(path_template, kwargs, _quote_path_segment_part) + + # Reject dot-segments (. and ..) in the final assembled path. The check + # runs after interpolation so that adjacent placeholders or a mix of static + # text and placeholders that together form a dot-segment are caught. + # Also reject percent-encoded dot-segments to protect against incorrectly + # implemented normalization in servers/proxies. + for segment in path_result.split("/"): + if _DOT_SEGMENT_RE.match(segment): + raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed") + + result = path_result + if query_template is not None: + result += "?" + _interpolate(query_template, kwargs, _quote_query_part) + if fragment_template is not None: + result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part) + + return result diff --git a/src/keycardai_api/resources/invitations.py b/src/keycardai_api/resources/invitations.py index 57f8339..923bb58 100644 --- a/src/keycardai_api/resources/invitations.py +++ b/src/keycardai_api/resources/invitations.py @@ -5,7 +5,7 @@ import httpx from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import strip_not_given +from .._utils import path_template, strip_not_given from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -69,7 +69,7 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `token` but received {token!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/invitations/{token}", + path_template("/invitations/{token}", token=token), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -108,7 +108,7 @@ def accept( raise ValueError(f"Expected a non-empty value for `token` but received {token!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/invitations/{token}/accept", + path_template("/invitations/{token}/accept", token=token), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -164,7 +164,7 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `token` but received {token!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/invitations/{token}", + path_template("/invitations/{token}", token=token), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -203,7 +203,7 @@ async def accept( raise ValueError(f"Expected a non-empty value for `token` but received {token!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/invitations/{token}/accept", + path_template("/invitations/{token}/accept", token=token), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/organizations/invitations.py b/src/keycardai_api/resources/organizations/invitations.py index 8a71cc2..ca9f974 100644 --- a/src/keycardai_api/resources/organizations/invitations.py +++ b/src/keycardai_api/resources/organizations/invitations.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -82,7 +82,7 @@ def create( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/organizations/{organization_id}/invitations", + path_template("/organizations/{organization_id}/invitations", organization_id=organization_id), body=maybe_transform( { "email": email, @@ -139,7 +139,7 @@ def list( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/invitations", + path_template("/organizations/{organization_id}/invitations", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -194,7 +194,11 @@ def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._delete( - f"/organizations/{organization_id}/invitations/{invitation_id}", + path_template( + "/organizations/{organization_id}/invitations/{invitation_id}", + organization_id=organization_id, + invitation_id=invitation_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -258,7 +262,7 @@ async def create( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/organizations/{organization_id}/invitations", + path_template("/organizations/{organization_id}/invitations", organization_id=organization_id), body=await async_maybe_transform( { "email": email, @@ -315,7 +319,7 @@ async def list( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/invitations", + path_template("/organizations/{organization_id}/invitations", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -370,7 +374,11 @@ async def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._delete( - f"/organizations/{organization_id}/invitations/{invitation_id}", + path_template( + "/organizations/{organization_id}/invitations/{invitation_id}", + organization_id=organization_id, + invitation_id=invitation_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/organizations/organizations.py b/src/keycardai_api/resources/organizations/organizations.py index 4678241..bf6ec16 100644 --- a/src/keycardai_api/resources/organizations/organizations.py +++ b/src/keycardai_api/resources/organizations/organizations.py @@ -25,7 +25,7 @@ organization_list_identities_params, ) from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -175,7 +175,7 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}", + path_template("/organizations/{organization_id}", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -219,7 +219,7 @@ def update( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._patch( - f"/organizations/{organization_id}", + path_template("/organizations/{organization_id}", organization_id=organization_id), body=maybe_transform({"name": name}, organization_update_params.OrganizationUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -314,7 +314,7 @@ def exchange_token( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/organizations/{organization_id}/token", + path_template("/organizations/{organization_id}/token", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -367,7 +367,7 @@ def list_identities( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/identities", + path_template("/organizations/{organization_id}/identities", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -433,7 +433,7 @@ def list_roles( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/roles", + path_template("/organizations/{organization_id}/roles", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -555,7 +555,7 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}", + path_template("/organizations/{organization_id}", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -601,7 +601,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._patch( - f"/organizations/{organization_id}", + path_template("/organizations/{organization_id}", organization_id=organization_id), body=await async_maybe_transform({"name": name}, organization_update_params.OrganizationUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -696,7 +696,7 @@ async def exchange_token( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/organizations/{organization_id}/token", + path_template("/organizations/{organization_id}/token", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -749,7 +749,7 @@ async def list_identities( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/identities", + path_template("/organizations/{organization_id}/identities", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -815,7 +815,7 @@ async def list_roles( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/roles", + path_template("/organizations/{organization_id}/roles", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/keycardai_api/resources/organizations/service_accounts/credentials.py b/src/keycardai_api/resources/organizations/service_accounts/credentials.py index 5b5e4fe..d316575 100644 --- a/src/keycardai_api/resources/organizations/service_accounts/credentials.py +++ b/src/keycardai_api/resources/organizations/service_accounts/credentials.py @@ -8,7 +8,7 @@ import httpx from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ...._utils import maybe_transform, strip_not_given, async_maybe_transform +from ...._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -92,7 +92,11 @@ def create( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + organization_id=organization_id, + service_account_id=service_account_id, + ), body=maybe_transform( { "name": name, @@ -150,7 +154,12 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + organization_id=organization_id, + service_account_id=service_account_id, + credential_id=credential_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -207,7 +216,12 @@ def update( raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._patch( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + organization_id=organization_id, + service_account_id=service_account_id, + credential_id=credential_id, + ), body=maybe_transform( { "description": description, @@ -269,7 +283,11 @@ def list( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + organization_id=organization_id, + service_account_id=service_account_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -329,7 +347,12 @@ def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._delete( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + organization_id=organization_id, + service_account_id=service_account_id, + credential_id=credential_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -398,7 +421,11 @@ async def create( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + organization_id=organization_id, + service_account_id=service_account_id, + ), body=await async_maybe_transform( { "name": name, @@ -456,7 +483,12 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + organization_id=organization_id, + service_account_id=service_account_id, + credential_id=credential_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -515,7 +547,12 @@ async def update( raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._patch( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + organization_id=organization_id, + service_account_id=service_account_id, + credential_id=credential_id, + ), body=await async_maybe_transform( { "description": description, @@ -577,7 +614,11 @@ async def list( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials", + organization_id=organization_id, + service_account_id=service_account_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -637,7 +678,12 @@ async def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._delete( - f"/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}/credentials/{credential_id}", + organization_id=organization_id, + service_account_id=service_account_id, + credential_id=credential_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py b/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py index 5817d28..2e6916b 100644 --- a/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py +++ b/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py @@ -8,7 +8,7 @@ import httpx from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ...._utils import maybe_transform, strip_not_given, async_maybe_transform +from ...._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ...._compat import cached_property from .credentials import ( CredentialsResource, @@ -98,7 +98,7 @@ def create( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/organizations/{organization_id}/service-accounts", + path_template("/organizations/{organization_id}/service-accounts", organization_id=organization_id), body=maybe_transform( { "name": name, @@ -151,7 +151,11 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/service-accounts/{service_account_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}", + organization_id=organization_id, + service_account_id=service_account_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -203,7 +207,11 @@ def update( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._patch( - f"/organizations/{organization_id}/service-accounts/{service_account_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}", + organization_id=organization_id, + service_account_id=service_account_id, + ), body=maybe_transform( { "description": description, @@ -260,7 +268,7 @@ def list( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/service-accounts", + path_template("/organizations/{organization_id}/service-accounts", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -315,7 +323,11 @@ def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._delete( - f"/organizations/{organization_id}/service-accounts/{service_account_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}", + organization_id=organization_id, + service_account_id=service_account_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -383,7 +395,7 @@ async def create( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/organizations/{organization_id}/service-accounts", + path_template("/organizations/{organization_id}/service-accounts", organization_id=organization_id), body=await async_maybe_transform( { "name": name, @@ -436,7 +448,11 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/service-accounts/{service_account_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}", + organization_id=organization_id, + service_account_id=service_account_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -490,7 +506,11 @@ async def update( raise ValueError(f"Expected a non-empty value for `service_account_id` but received {service_account_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._patch( - f"/organizations/{organization_id}/service-accounts/{service_account_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}", + organization_id=organization_id, + service_account_id=service_account_id, + ), body=await async_maybe_transform( { "description": description, @@ -547,7 +567,7 @@ async def list( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/service-accounts", + path_template("/organizations/{organization_id}/service-accounts", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -602,7 +622,11 @@ async def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._delete( - f"/organizations/{organization_id}/service-accounts/{service_account_id}", + path_template( + "/organizations/{organization_id}/service-accounts/{service_account_id}", + organization_id=organization_id, + service_account_id=service_account_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/organizations/sso_connection.py b/src/keycardai_api/resources/organizations/sso_connection.py index 7c4ddac..9657687 100644 --- a/src/keycardai_api/resources/organizations/sso_connection.py +++ b/src/keycardai_api/resources/organizations/sso_connection.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -83,7 +83,7 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -136,7 +136,7 @@ def update( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._patch( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), body=maybe_transform( { "client_id": client_id, @@ -183,7 +183,7 @@ def disable( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._delete( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -232,7 +232,7 @@ def enable( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), body=maybe_transform( { "client_id": client_id, @@ -303,7 +303,7 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -358,7 +358,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._patch( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), body=await async_maybe_transform( { "client_id": client_id, @@ -405,7 +405,7 @@ async def disable( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._delete( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -454,7 +454,7 @@ async def enable( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/organizations/{organization_id}/sso-connection", + path_template("/organizations/{organization_id}/sso-connection", organization_id=organization_id), body=await async_maybe_transform( { "client_id": client_id, diff --git a/src/keycardai_api/resources/organizations/users.py b/src/keycardai_api/resources/organizations/users.py index 29e0b12..8f53d68 100644 --- a/src/keycardai_api/resources/organizations/users.py +++ b/src/keycardai_api/resources/organizations/users.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -92,7 +92,9 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/users/{user_id}", + path_template( + "/organizations/{organization_id}/users/{user_id}", organization_id=organization_id, user_id=user_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -144,7 +146,9 @@ def update( raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._patch( - f"/organizations/{organization_id}/users/{user_id}", + path_template( + "/organizations/{organization_id}/users/{user_id}", organization_id=organization_id, user_id=user_id + ), body=maybe_transform( { "role": role, @@ -204,7 +208,7 @@ def list( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/organizations/{organization_id}/users", + path_template("/organizations/{organization_id}/users", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -260,7 +264,9 @@ def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._delete( - f"/organizations/{organization_id}/users/{user_id}", + path_template( + "/organizations/{organization_id}/users/{user_id}", organization_id=organization_id, user_id=user_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -327,7 +333,9 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/users/{user_id}", + path_template( + "/organizations/{organization_id}/users/{user_id}", organization_id=organization_id, user_id=user_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -379,7 +387,9 @@ async def update( raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._patch( - f"/organizations/{organization_id}/users/{user_id}", + path_template( + "/organizations/{organization_id}/users/{user_id}", organization_id=organization_id, user_id=user_id + ), body=await async_maybe_transform( { "role": role, @@ -439,7 +449,7 @@ async def list( raise ValueError(f"Expected a non-empty value for `organization_id` but received {organization_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/organizations/{organization_id}/users", + path_template("/organizations/{organization_id}/users", organization_id=organization_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -495,7 +505,9 @@ async def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._delete( - f"/organizations/{organization_id}/users/{user_id}", + path_template( + "/organizations/{organization_id}/users/{user_id}", organization_id=organization_id, user_id=user_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/application_credentials.py b/src/keycardai_api/resources/zones/application_credentials.py index 1d2abf7..14e9b18 100644 --- a/src/keycardai_api/resources/zones/application_credentials.py +++ b/src/keycardai_api/resources/zones/application_credentials.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import required_args, maybe_transform, async_maybe_transform +from ..._utils import path_template, required_args, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -253,7 +253,7 @@ def create( return cast( ApplicationCredentialCreateResponse, self._post( - f"/zones/{zone_id}/application-credentials", + path_template("/zones/{zone_id}/application-credentials", zone_id=zone_id), body=maybe_transform( { "application_id": application_id, @@ -305,7 +305,7 @@ def retrieve( return cast( Credential, self._get( - f"/zones/{zone_id}/application-credentials/{id}", + path_template("/zones/{zone_id}/application-credentials/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -491,7 +491,7 @@ def update( return cast( Credential, self._patch( - f"/zones/{zone_id}/application-credentials/{id}", + path_template("/zones/{zone_id}/application-credentials/{id}", zone_id=zone_id, id=id), body=maybe_transform( { "subject": subject, @@ -546,7 +546,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/application-credentials", + path_template("/zones/{zone_id}/application-credentials", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -598,7 +598,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/application-credentials/{id}", + path_template("/zones/{zone_id}/application-credentials/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -829,7 +829,7 @@ async def create( return cast( ApplicationCredentialCreateResponse, await self._post( - f"/zones/{zone_id}/application-credentials", + path_template("/zones/{zone_id}/application-credentials", zone_id=zone_id), body=await async_maybe_transform( { "application_id": application_id, @@ -881,7 +881,7 @@ async def retrieve( return cast( Credential, await self._get( - f"/zones/{zone_id}/application-credentials/{id}", + path_template("/zones/{zone_id}/application-credentials/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1067,7 +1067,7 @@ async def update( return cast( Credential, await self._patch( - f"/zones/{zone_id}/application-credentials/{id}", + path_template("/zones/{zone_id}/application-credentials/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform( { "subject": subject, @@ -1122,7 +1122,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/application-credentials", + path_template("/zones/{zone_id}/application-credentials", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -1174,7 +1174,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/application-credentials/{id}", + path_template("/zones/{zone_id}/application-credentials/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/applications/applications.py b/src/keycardai_api/resources/zones/applications/applications.py index b03bbfa..da8b945 100644 --- a/src/keycardai_api/resources/zones/applications/applications.py +++ b/src/keycardai_api/resources/zones/applications/applications.py @@ -8,7 +8,7 @@ import httpx from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ...._utils import maybe_transform, async_maybe_transform +from ...._utils import path_template, maybe_transform, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -113,7 +113,7 @@ def create( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._post( - f"/zones/{zone_id}/applications", + path_template("/zones/{zone_id}/applications", zone_id=zone_id), body=maybe_transform( { "identifier": identifier, @@ -160,7 +160,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/applications/{id}", + path_template("/zones/{zone_id}/applications/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -211,7 +211,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._patch( - f"/zones/{zone_id}/applications/{id}", + path_template("/zones/{zone_id}/applications/{id}", zone_id=zone_id, id=id), body=maybe_transform( { "description": description, @@ -275,7 +275,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/applications", + path_template("/zones/{zone_id}/applications", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -329,7 +329,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/applications/{id}", + path_template("/zones/{zone_id}/applications/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -376,7 +376,7 @@ def list_credentials( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/applications/{id}/application-credentials", + path_template("/zones/{zone_id}/applications/{id}/application-credentials", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -436,7 +436,7 @@ def list_resources( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/applications/{id}/resources", + path_template("/zones/{zone_id}/applications/{id}/resources", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -526,7 +526,7 @@ async def create( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._post( - f"/zones/{zone_id}/applications", + path_template("/zones/{zone_id}/applications", zone_id=zone_id), body=await async_maybe_transform( { "identifier": identifier, @@ -573,7 +573,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/applications/{id}", + path_template("/zones/{zone_id}/applications/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -624,7 +624,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._patch( - f"/zones/{zone_id}/applications/{id}", + path_template("/zones/{zone_id}/applications/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform( { "description": description, @@ -688,7 +688,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/applications", + path_template("/zones/{zone_id}/applications", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -742,7 +742,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/applications/{id}", + path_template("/zones/{zone_id}/applications/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -789,7 +789,7 @@ async def list_credentials( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/applications/{id}/application-credentials", + path_template("/zones/{zone_id}/applications/{id}/application-credentials", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -849,7 +849,7 @@ async def list_resources( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/applications/{id}/resources", + path_template("/zones/{zone_id}/applications/{id}/resources", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/keycardai_api/resources/zones/applications/dependencies.py b/src/keycardai_api/resources/zones/applications/dependencies.py index 626c895..2576e00 100644 --- a/src/keycardai_api/resources/zones/applications/dependencies.py +++ b/src/keycardai_api/resources/zones/applications/dependencies.py @@ -8,7 +8,7 @@ import httpx from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given -from ...._utils import maybe_transform, async_maybe_transform +from ...._utils import path_template, maybe_transform, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -77,7 +77,12 @@ def retrieve( if not dependency_id: raise ValueError(f"Expected a non-empty value for `dependency_id` but received {dependency_id!r}") return self._get( - f"/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + path_template( + "/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + zone_id=zone_id, + id=id, + dependency_id=dependency_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -125,7 +130,7 @@ def list( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/applications/{id}/dependencies", + path_template("/zones/{zone_id}/applications/{id}/dependencies", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -180,7 +185,12 @@ def add( raise ValueError(f"Expected a non-empty value for `dependency_id` but received {dependency_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._put( - f"/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + path_template( + "/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + zone_id=zone_id, + id=id, + dependency_id=dependency_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -224,7 +234,12 @@ def remove( raise ValueError(f"Expected a non-empty value for `dependency_id` but received {dependency_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + path_template( + "/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + zone_id=zone_id, + id=id, + dependency_id=dependency_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -284,7 +299,12 @@ async def retrieve( if not dependency_id: raise ValueError(f"Expected a non-empty value for `dependency_id` but received {dependency_id!r}") return await self._get( - f"/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + path_template( + "/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + zone_id=zone_id, + id=id, + dependency_id=dependency_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -332,7 +352,7 @@ async def list( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/applications/{id}/dependencies", + path_template("/zones/{zone_id}/applications/{id}/dependencies", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -387,7 +407,12 @@ async def add( raise ValueError(f"Expected a non-empty value for `dependency_id` but received {dependency_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._put( - f"/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + path_template( + "/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + zone_id=zone_id, + id=id, + dependency_id=dependency_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -433,7 +458,12 @@ async def remove( raise ValueError(f"Expected a non-empty value for `dependency_id` but received {dependency_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + path_template( + "/zones/{zone_id}/applications/{id}/dependencies/{dependency_id}", + zone_id=zone_id, + id=id, + dependency_id=dependency_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/delegated_grants.py b/src/keycardai_api/resources/zones/delegated_grants.py index 60f5f17..9287fa8 100644 --- a/src/keycardai_api/resources/zones/delegated_grants.py +++ b/src/keycardai_api/resources/zones/delegated_grants.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -74,7 +74,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/delegated-grants/{id}", + path_template("/zones/{zone_id}/delegated-grants/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -111,7 +111,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._patch( - f"/zones/{zone_id}/delegated-grants/{id}", + path_template("/zones/{zone_id}/delegated-grants/{id}", zone_id=zone_id, id=id), body=maybe_transform({"status": status}, delegated_grant_update_params.DelegatedGrantUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -165,7 +165,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/delegated-grants", + path_template("/zones/{zone_id}/delegated-grants", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -219,7 +219,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/delegated-grants/{id}", + path_template("/zones/{zone_id}/delegated-grants/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -276,7 +276,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/delegated-grants/{id}", + path_template("/zones/{zone_id}/delegated-grants/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -313,7 +313,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._patch( - f"/zones/{zone_id}/delegated-grants/{id}", + path_template("/zones/{zone_id}/delegated-grants/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform( {"status": status}, delegated_grant_update_params.DelegatedGrantUpdateParams ), @@ -369,7 +369,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/delegated-grants", + path_template("/zones/{zone_id}/delegated-grants", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -423,7 +423,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/delegated-grants/{id}", + path_template("/zones/{zone_id}/delegated-grants/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/members.py b/src/keycardai_api/resources/zones/members.py index e6298b5..2c0acb9 100644 --- a/src/keycardai_api/resources/zones/members.py +++ b/src/keycardai_api/resources/zones/members.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -77,7 +77,11 @@ def retrieve( f"Expected a non-empty value for `organization_user_id` but received {organization_user_id!r}" ) return self._get( - f"/zones/{zone_id}/members/{organization_user_id}", + path_template( + "/zones/{zone_id}/members/{organization_user_id}", + zone_id=zone_id, + organization_user_id=organization_user_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -121,7 +125,11 @@ def update( f"Expected a non-empty value for `organization_user_id` but received {organization_user_id!r}" ) return self._patch( - f"/zones/{zone_id}/members/{organization_user_id}", + path_template( + "/zones/{zone_id}/members/{organization_user_id}", + zone_id=zone_id, + organization_user_id=organization_user_id, + ), body=maybe_transform({"role": role}, member_update_params.MemberUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -170,7 +178,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/members", + path_template("/zones/{zone_id}/members", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -224,7 +232,11 @@ def delete( ) extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/members/{organization_user_id}", + path_template( + "/zones/{zone_id}/members/{organization_user_id}", + zone_id=zone_id, + organization_user_id=organization_user_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -264,7 +276,7 @@ def add( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._post( - f"/zones/{zone_id}/members", + path_template("/zones/{zone_id}/members", zone_id=zone_id), body=maybe_transform( { "organization_user_id": organization_user_id, @@ -330,7 +342,11 @@ async def retrieve( f"Expected a non-empty value for `organization_user_id` but received {organization_user_id!r}" ) return await self._get( - f"/zones/{zone_id}/members/{organization_user_id}", + path_template( + "/zones/{zone_id}/members/{organization_user_id}", + zone_id=zone_id, + organization_user_id=organization_user_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -374,7 +390,11 @@ async def update( f"Expected a non-empty value for `organization_user_id` but received {organization_user_id!r}" ) return await self._patch( - f"/zones/{zone_id}/members/{organization_user_id}", + path_template( + "/zones/{zone_id}/members/{organization_user_id}", + zone_id=zone_id, + organization_user_id=organization_user_id, + ), body=await async_maybe_transform({"role": role}, member_update_params.MemberUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -423,7 +443,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/members", + path_template("/zones/{zone_id}/members", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -477,7 +497,11 @@ async def delete( ) extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/members/{organization_user_id}", + path_template( + "/zones/{zone_id}/members/{organization_user_id}", + zone_id=zone_id, + organization_user_id=organization_user_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -517,7 +541,7 @@ async def add( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._post( - f"/zones/{zone_id}/members", + path_template("/zones/{zone_id}/members", zone_id=zone_id), body=await async_maybe_transform( { "organization_user_id": organization_user_id, diff --git a/src/keycardai_api/resources/zones/policies/policies.py b/src/keycardai_api/resources/zones/policies/policies.py index b697838..5b19a97 100644 --- a/src/keycardai_api/resources/zones/policies/policies.py +++ b/src/keycardai_api/resources/zones/policies/policies.py @@ -16,7 +16,7 @@ AsyncVersionsResourceWithStreamingResponse, ) from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ...._utils import maybe_transform, strip_not_given, async_maybe_transform +from ...._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -99,7 +99,7 @@ def create( **(extra_headers or {}), } return self._post( - f"/zones/{zone_id}/policies", + path_template("/zones/{zone_id}/policies", zone_id=zone_id), body=maybe_transform( { "name": name, @@ -153,7 +153,7 @@ def retrieve( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policies/{policy_id}", + path_template("/zones/{zone_id}/policies/{policy_id}", zone_id=zone_id, policy_id=policy_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -204,7 +204,7 @@ def update( **(extra_headers or {}), } return self._patch( - f"/zones/{zone_id}/policies/{policy_id}", + path_template("/zones/{zone_id}/policies/{policy_id}", zone_id=zone_id, policy_id=policy_id), body=maybe_transform( { "description": description, @@ -275,7 +275,7 @@ def list( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policies", + path_template("/zones/{zone_id}/policies", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -336,7 +336,7 @@ def archive( **(extra_headers or {}), } return self._delete( - f"/zones/{zone_id}/policies/{policy_id}", + path_template("/zones/{zone_id}/policies/{policy_id}", zone_id=zone_id, policy_id=policy_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -410,7 +410,7 @@ async def create( **(extra_headers or {}), } return await self._post( - f"/zones/{zone_id}/policies", + path_template("/zones/{zone_id}/policies", zone_id=zone_id), body=await async_maybe_transform( { "name": name, @@ -464,7 +464,7 @@ async def retrieve( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policies/{policy_id}", + path_template("/zones/{zone_id}/policies/{policy_id}", zone_id=zone_id, policy_id=policy_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -515,7 +515,7 @@ async def update( **(extra_headers or {}), } return await self._patch( - f"/zones/{zone_id}/policies/{policy_id}", + path_template("/zones/{zone_id}/policies/{policy_id}", zone_id=zone_id, policy_id=policy_id), body=await async_maybe_transform( { "description": description, @@ -586,7 +586,7 @@ async def list( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policies", + path_template("/zones/{zone_id}/policies", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -647,7 +647,7 @@ async def archive( **(extra_headers or {}), } return await self._delete( - f"/zones/{zone_id}/policies/{policy_id}", + path_template("/zones/{zone_id}/policies/{policy_id}", zone_id=zone_id, policy_id=policy_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/policies/versions.py b/src/keycardai_api/resources/zones/policies/versions.py index 950f2d0..1caa111 100644 --- a/src/keycardai_api/resources/zones/policies/versions.py +++ b/src/keycardai_api/resources/zones/policies/versions.py @@ -8,7 +8,7 @@ import httpx from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ...._utils import maybe_transform, strip_not_given, async_maybe_transform +from ...._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -96,7 +96,7 @@ def create( **(extra_headers or {}), } return self._post( - f"/zones/{zone_id}/policies/{policy_id}/versions", + path_template("/zones/{zone_id}/policies/{policy_id}/versions", zone_id=zone_id, policy_id=policy_id), body=maybe_transform( { "schema_version": schema_version, @@ -159,7 +159,12 @@ def retrieve( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + zone_id=zone_id, + policy_id=policy_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -234,7 +239,7 @@ def list( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policies/{policy_id}/versions", + path_template("/zones/{zone_id}/policies/{policy_id}/versions", zone_id=zone_id, policy_id=policy_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -299,7 +304,12 @@ def archive( **(extra_headers or {}), } return self._delete( - f"/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + zone_id=zone_id, + policy_id=policy_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -378,7 +388,7 @@ async def create( **(extra_headers or {}), } return await self._post( - f"/zones/{zone_id}/policies/{policy_id}/versions", + path_template("/zones/{zone_id}/policies/{policy_id}/versions", zone_id=zone_id, policy_id=policy_id), body=await async_maybe_transform( { "schema_version": schema_version, @@ -441,7 +451,12 @@ async def retrieve( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + zone_id=zone_id, + policy_id=policy_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -516,7 +531,7 @@ async def list( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policies/{policy_id}/versions", + path_template("/zones/{zone_id}/policies/{policy_id}/versions", zone_id=zone_id, policy_id=policy_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -581,7 +596,12 @@ async def archive( **(extra_headers or {}), } return await self._delete( - f"/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policies/{policy_id}/versions/{version_id}", + zone_id=zone_id, + policy_id=policy_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/policy_schemas.py b/src/keycardai_api/resources/zones/policy_schemas.py index 66c7f2e..6d1d161 100644 --- a/src/keycardai_api/resources/zones/policy_schemas.py +++ b/src/keycardai_api/resources/zones/policy_schemas.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -107,7 +107,7 @@ def retrieve( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-schemas/{version}", + path_template("/zones/{zone_id}/policy-schemas/{version}", zone_id=zone_id, version=version), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -184,7 +184,7 @@ def list( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-schemas", + path_template("/zones/{zone_id}/policy-schemas", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -248,7 +248,7 @@ def set_default( **(extra_headers or {}), } return self._patch( - f"/zones/{zone_id}/policy-schemas/{version}", + path_template("/zones/{zone_id}/policy-schemas/{version}", zone_id=zone_id, version=version), body=maybe_transform(body, policy_schema_set_default_params.PolicySchemaSetDefaultParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -339,7 +339,7 @@ async def retrieve( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-schemas/{version}", + path_template("/zones/{zone_id}/policy-schemas/{version}", zone_id=zone_id, version=version), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -418,7 +418,7 @@ async def list( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-schemas", + path_template("/zones/{zone_id}/policy-schemas", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -482,7 +482,7 @@ async def set_default( **(extra_headers or {}), } return await self._patch( - f"/zones/{zone_id}/policy-schemas/{version}", + path_template("/zones/{zone_id}/policy-schemas/{version}", zone_id=zone_id, version=version), body=await async_maybe_transform(body, policy_schema_set_default_params.PolicySchemaSetDefaultParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout diff --git a/src/keycardai_api/resources/zones/policy_sets/policy_sets.py b/src/keycardai_api/resources/zones/policy_sets/policy_sets.py index 7ab3357..0445925 100644 --- a/src/keycardai_api/resources/zones/policy_sets/policy_sets.py +++ b/src/keycardai_api/resources/zones/policy_sets/policy_sets.py @@ -16,7 +16,7 @@ AsyncVersionsResourceWithStreamingResponse, ) from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ...._utils import maybe_transform, strip_not_given, async_maybe_transform +from ...._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -109,7 +109,7 @@ def create( **(extra_headers or {}), } return self._post( - f"/zones/{zone_id}/policy-sets", + path_template("/zones/{zone_id}/policy-sets", zone_id=zone_id), body=maybe_transform( { "name": name, @@ -163,7 +163,7 @@ def retrieve( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}", + path_template("/zones/{zone_id}/policy-sets/{policy_set_id}", zone_id=zone_id, policy_set_id=policy_set_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -215,7 +215,7 @@ def update( **(extra_headers or {}), } return self._patch( - f"/zones/{zone_id}/policy-sets/{policy_set_id}", + path_template("/zones/{zone_id}/policy-sets/{policy_set_id}", zone_id=zone_id, policy_set_id=policy_set_id), body=maybe_transform({"name": name}, policy_set_update_params.PolicySetUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -280,7 +280,7 @@ def list( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-sets", + path_template("/zones/{zone_id}/policy-sets", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -343,7 +343,7 @@ def archive( **(extra_headers or {}), } return self._delete( - f"/zones/{zone_id}/policy-sets/{policy_set_id}", + path_template("/zones/{zone_id}/policy-sets/{policy_set_id}", zone_id=zone_id, policy_set_id=policy_set_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -427,7 +427,7 @@ async def create( **(extra_headers or {}), } return await self._post( - f"/zones/{zone_id}/policy-sets", + path_template("/zones/{zone_id}/policy-sets", zone_id=zone_id), body=await async_maybe_transform( { "name": name, @@ -481,7 +481,7 @@ async def retrieve( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}", + path_template("/zones/{zone_id}/policy-sets/{policy_set_id}", zone_id=zone_id, policy_set_id=policy_set_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -533,7 +533,7 @@ async def update( **(extra_headers or {}), } return await self._patch( - f"/zones/{zone_id}/policy-sets/{policy_set_id}", + path_template("/zones/{zone_id}/policy-sets/{policy_set_id}", zone_id=zone_id, policy_set_id=policy_set_id), body=await async_maybe_transform({"name": name}, policy_set_update_params.PolicySetUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -598,7 +598,7 @@ async def list( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-sets", + path_template("/zones/{zone_id}/policy-sets", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -661,7 +661,7 @@ async def archive( **(extra_headers or {}), } return await self._delete( - f"/zones/{zone_id}/policy-sets/{policy_set_id}", + path_template("/zones/{zone_id}/policy-sets/{policy_set_id}", zone_id=zone_id, policy_set_id=policy_set_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/policy_sets/versions.py b/src/keycardai_api/resources/zones/policy_sets/versions.py index c07fb7a..0d8a521 100644 --- a/src/keycardai_api/resources/zones/policy_sets/versions.py +++ b/src/keycardai_api/resources/zones/policy_sets/versions.py @@ -8,7 +8,7 @@ import httpx from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ...._utils import maybe_transform, strip_not_given, async_maybe_transform +from ...._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource from ...._response import ( @@ -98,7 +98,9 @@ def create( **(extra_headers or {}), } return self._post( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions", zone_id=zone_id, policy_set_id=policy_set_id + ), body=maybe_transform( { "manifest": manifest, @@ -155,7 +157,12 @@ def retrieve( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -208,7 +215,12 @@ def update( **(extra_headers or {}), } return self._patch( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), body=maybe_transform({"active": active}, version_update_params.VersionUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -276,7 +288,9 @@ def list( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions", zone_id=zone_id, policy_set_id=policy_set_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -340,7 +354,12 @@ def archive( **(extra_headers or {}), } return self._delete( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -415,7 +434,12 @@ def list_policies( **(extra_headers or {}), } return self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}/policies", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}/policies", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -504,7 +528,9 @@ async def create( **(extra_headers or {}), } return await self._post( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions", zone_id=zone_id, policy_set_id=policy_set_id + ), body=await async_maybe_transform( { "manifest": manifest, @@ -561,7 +587,12 @@ async def retrieve( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -614,7 +645,12 @@ async def update( **(extra_headers or {}), } return await self._patch( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), body=await async_maybe_transform({"active": active}, version_update_params.VersionUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -682,7 +718,9 @@ async def list( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions", zone_id=zone_id, policy_set_id=policy_set_id + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -746,7 +784,12 @@ async def archive( **(extra_headers or {}), } return await self._delete( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -821,7 +864,12 @@ async def list_policies( **(extra_headers or {}), } return await self._get( - f"/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}/policies", + path_template( + "/zones/{zone_id}/policy-sets/{policy_set_id}/versions/{version_id}/policies", + zone_id=zone_id, + policy_set_id=policy_set_id, + version_id=version_id, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/keycardai_api/resources/zones/providers.py b/src/keycardai_api/resources/zones/providers.py index 8b22181..91273c4 100644 --- a/src/keycardai_api/resources/zones/providers.py +++ b/src/keycardai_api/resources/zones/providers.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -93,7 +93,7 @@ def create( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._post( - f"/zones/{zone_id}/providers", + path_template("/zones/{zone_id}/providers", zone_id=zone_id), body=maybe_transform( { "identifier": identifier, @@ -141,7 +141,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/providers/{id}", + path_template("/zones/{zone_id}/providers/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -199,7 +199,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._patch( - f"/zones/{zone_id}/providers/{id}", + path_template("/zones/{zone_id}/providers/{id}", zone_id=zone_id, id=id), body=maybe_transform( { "client_id": client_id, @@ -258,7 +258,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/providers", + path_template("/zones/{zone_id}/providers", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -311,7 +311,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/providers/{id}", + path_template("/zones/{zone_id}/providers/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -387,7 +387,7 @@ async def create( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._post( - f"/zones/{zone_id}/providers", + path_template("/zones/{zone_id}/providers", zone_id=zone_id), body=await async_maybe_transform( { "identifier": identifier, @@ -435,7 +435,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/providers/{id}", + path_template("/zones/{zone_id}/providers/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -493,7 +493,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._patch( - f"/zones/{zone_id}/providers/{id}", + path_template("/zones/{zone_id}/providers/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform( { "client_id": client_id, @@ -552,7 +552,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/providers", + path_template("/zones/{zone_id}/providers", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -605,7 +605,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/providers/{id}", + path_template("/zones/{zone_id}/providers/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/resources.py b/src/keycardai_api/resources/zones/resources.py index 5bc6e73..88404c1 100644 --- a/src/keycardai_api/resources/zones/resources.py +++ b/src/keycardai_api/resources/zones/resources.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -104,7 +104,7 @@ def create( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._post( - f"/zones/{zone_id}/resources", + path_template("/zones/{zone_id}/resources", zone_id=zone_id), body=maybe_transform( { "identifier": identifier, @@ -153,7 +153,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/resources/{id}", + path_template("/zones/{zone_id}/resources/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -216,7 +216,7 @@ def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._patch( - f"/zones/{zone_id}/resources/{id}", + path_template("/zones/{zone_id}/resources/{id}", zone_id=zone_id, id=id), body=maybe_transform( { "application_id": application_id, @@ -279,7 +279,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/resources", + path_template("/zones/{zone_id}/resources", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -331,7 +331,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/resources/{id}", + path_template("/zones/{zone_id}/resources/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -412,7 +412,7 @@ async def create( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._post( - f"/zones/{zone_id}/resources", + path_template("/zones/{zone_id}/resources", zone_id=zone_id), body=await async_maybe_transform( { "identifier": identifier, @@ -461,7 +461,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/resources/{id}", + path_template("/zones/{zone_id}/resources/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -524,7 +524,7 @@ async def update( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._patch( - f"/zones/{zone_id}/resources/{id}", + path_template("/zones/{zone_id}/resources/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform( { "application_id": application_id, @@ -587,7 +587,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/resources", + path_template("/zones/{zone_id}/resources", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -639,7 +639,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/resources/{id}", + path_template("/zones/{zone_id}/resources/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/secrets.py b/src/keycardai_api/resources/zones/secrets.py index 96218e0..bce28cd 100644 --- a/src/keycardai_api/resources/zones/secrets.py +++ b/src/keycardai_api/resources/zones/secrets.py @@ -7,7 +7,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -91,7 +91,7 @@ def create( raise ValueError(f"Expected a non-empty value for `path_zone_id` but received {path_zone_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._post( - f"/zones/{path_zone_id}/secrets", + path_template("/zones/{path_zone_id}/secrets", path_zone_id=path_zone_id), body=maybe_transform( { "data": data, @@ -140,7 +140,7 @@ def retrieve( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/zones/{zone_id}/secrets/{id}", + path_template("/zones/{zone_id}/secrets/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -188,7 +188,7 @@ def update( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._patch( - f"/zones/{zone_id}/secrets/{id}", + path_template("/zones/{zone_id}/secrets/{id}", zone_id=zone_id, id=id), body=maybe_transform( { "data": data, @@ -238,7 +238,7 @@ def list( raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._get( - f"/zones/{zone_id}/secrets", + path_template("/zones/{zone_id}/secrets", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -287,7 +287,7 @@ def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/secrets/{id}", + path_template("/zones/{zone_id}/secrets/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -361,7 +361,7 @@ async def create( raise ValueError(f"Expected a non-empty value for `path_zone_id` but received {path_zone_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._post( - f"/zones/{path_zone_id}/secrets", + path_template("/zones/{path_zone_id}/secrets", path_zone_id=path_zone_id), body=await async_maybe_transform( { "data": data, @@ -410,7 +410,7 @@ async def retrieve( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/zones/{zone_id}/secrets/{id}", + path_template("/zones/{zone_id}/secrets/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -458,7 +458,7 @@ async def update( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._patch( - f"/zones/{zone_id}/secrets/{id}", + path_template("/zones/{zone_id}/secrets/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform( { "data": data, @@ -508,7 +508,7 @@ async def list( raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._get( - f"/zones/{zone_id}/secrets", + path_template("/zones/{zone_id}/secrets", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -557,7 +557,7 @@ async def delete( extra_headers = {"Accept": "*/*", **(extra_headers or {})} extra_headers = {**strip_not_given({"X-Client-Request-ID": x_client_request_id}), **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/secrets/{id}", + path_template("/zones/{zone_id}/secrets/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/sessions.py b/src/keycardai_api/resources/zones/sessions.py index 1e1b4bf..3a929ca 100644 --- a/src/keycardai_api/resources/zones/sessions.py +++ b/src/keycardai_api/resources/zones/sessions.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -76,7 +76,7 @@ def retrieve( return cast( Session, self._get( - f"/zones/{zone_id}/sessions/{id}", + path_template("/zones/{zone_id}/sessions/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -116,7 +116,7 @@ def update( return cast( Session, self._patch( - f"/zones/{zone_id}/sessions/{id}", + path_template("/zones/{zone_id}/sessions/{id}", zone_id=zone_id, id=id), body=maybe_transform({"status": status}, session_update_params.SessionUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -176,7 +176,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/sessions", + path_template("/zones/{zone_id}/sessions", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -230,7 +230,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}/sessions/{id}", + path_template("/zones/{zone_id}/sessions/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -289,7 +289,7 @@ async def retrieve( return cast( Session, await self._get( - f"/zones/{zone_id}/sessions/{id}", + path_template("/zones/{zone_id}/sessions/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -329,7 +329,7 @@ async def update( return cast( Session, await self._patch( - f"/zones/{zone_id}/sessions/{id}", + path_template("/zones/{zone_id}/sessions/{id}", zone_id=zone_id, id=id), body=await async_maybe_transform({"status": status}, session_update_params.SessionUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -389,7 +389,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/sessions", + path_template("/zones/{zone_id}/sessions", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -443,7 +443,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}/sessions/{id}", + path_template("/zones/{zone_id}/sessions/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/keycardai_api/resources/zones/user_agents.py b/src/keycardai_api/resources/zones/user_agents.py index 79c64de..9f43cc1 100644 --- a/src/keycardai_api/resources/zones/user_agents.py +++ b/src/keycardai_api/resources/zones/user_agents.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -74,7 +74,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/user-agents/{id}", + path_template("/zones/{zone_id}/user-agents/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -120,7 +120,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/user-agents", + path_template("/zones/{zone_id}/user-agents", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -189,7 +189,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/user-agents/{id}", + path_template("/zones/{zone_id}/user-agents/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -235,7 +235,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/user-agents", + path_template("/zones/{zone_id}/user-agents", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/keycardai_api/resources/zones/users.py b/src/keycardai_api/resources/zones/users.py index 1d4022e..4edd9f4 100644 --- a/src/keycardai_api/resources/zones/users.py +++ b/src/keycardai_api/resources/zones/users.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -74,7 +74,7 @@ def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/zones/{zone_id}/users/{id}", + path_template("/zones/{zone_id}/users/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -118,7 +118,7 @@ def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}/users", + path_template("/zones/{zone_id}/users", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -187,7 +187,7 @@ async def retrieve( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/zones/{zone_id}/users/{id}", + path_template("/zones/{zone_id}/users/{id}", zone_id=zone_id, id=id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -231,7 +231,7 @@ async def list( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}/users", + path_template("/zones/{zone_id}/users", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/keycardai_api/resources/zones/zones.py b/src/keycardai_api/resources/zones/zones.py index 6598977..1c00a24 100644 --- a/src/keycardai_api/resources/zones/zones.py +++ b/src/keycardai_api/resources/zones/zones.py @@ -38,7 +38,7 @@ AsyncSecretsResourceWithStreamingResponse, ) from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from .sessions import ( SessionsResource, AsyncSessionsResource, @@ -320,7 +320,7 @@ def retrieve( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._get( - f"/zones/{zone_id}", + path_template("/zones/{zone_id}", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -386,7 +386,7 @@ def update( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return self._patch( - f"/zones/{zone_id}", + path_template("/zones/{zone_id}", zone_id=zone_id), body=maybe_transform( { "default_mcp_gateway_application_id": default_mcp_gateway_application_id, @@ -489,7 +489,7 @@ def delete( raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( - f"/zones/{zone_id}", + path_template("/zones/{zone_id}", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -682,7 +682,7 @@ async def retrieve( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._get( - f"/zones/{zone_id}", + path_template("/zones/{zone_id}", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -748,7 +748,7 @@ async def update( if not zone_id: raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") return await self._patch( - f"/zones/{zone_id}", + path_template("/zones/{zone_id}", zone_id=zone_id), body=await async_maybe_transform( { "default_mcp_gateway_application_id": default_mcp_gateway_application_id, @@ -851,7 +851,7 @@ async def delete( raise ValueError(f"Expected a non-empty value for `zone_id` but received {zone_id!r}") extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( - f"/zones/{zone_id}", + path_template("/zones/{zone_id}", zone_id=zone_id), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/tests/test_utils/test_path.py b/tests/test_utils/test_path.py new file mode 100644 index 0000000..527cc60 --- /dev/null +++ b/tests/test_utils/test_path.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from keycardai_api._utils._path import path_template + + +@pytest.mark.parametrize( + "template, kwargs, expected", + [ + ("/v1/{id}", dict(id="abc"), "/v1/abc"), + ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"), + ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"), + ("/{w}/{w}", dict(w="echo"), "/echo/echo"), + ("/v1/static", {}, "/v1/static"), + ("", {}, ""), + ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"), + ("/v1/{v}", dict(v=None), "/v1/null"), + ("/v1/{v}", dict(v=True), "/v1/true"), + ("/v1/{v}", dict(v=False), "/v1/false"), + ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok + ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok + ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok + ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok + ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine + ( + "/v1/{a}?query={b}", + dict(a="../../other/endpoint", b="a&bad=true"), + "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue", + ), + ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"), + ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"), + ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"), + ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input + # Query: slash and ? are safe, # is not + ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"), + ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"), + ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"), + ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"), + # Fragment: slash and ? are safe + ("/docs#{v}", dict(v="a/b"), "/docs#a/b"), + ("/docs#{v}", dict(v="a?b"), "/docs#a?b"), + # Path: slash, ? and # are all encoded + ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"), + ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"), + ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"), + # same var encoded differently by component + ( + "/v1/{v}?q={v}#{v}", + dict(v="a/b?c#d"), + "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d", + ), + ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection + ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection + ], +) +def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None: + assert path_template(template, **kwargs) == expected + + +def test_missing_kwarg_raises_key_error() -> None: + with pytest.raises(KeyError, match="org_id"): + path_template("/v1/{org_id}") + + +@pytest.mark.parametrize( + "template, kwargs", + [ + ("{a}/path", dict(a=".")), + ("{a}/path", dict(a="..")), + ("/v1/{a}", dict(a=".")), + ("/v1/{a}", dict(a="..")), + ("/v1/{a}/path", dict(a=".")), + ("/v1/{a}/path", dict(a="..")), + ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".." + ("/v1/{a}.", dict(a=".")), # var + static → ".." + ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "." + ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text + ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static + ("/v1/{v}?q=1", dict(v="..")), + ("/v1/{v}#frag", dict(v="..")), + ], +) +def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None: + with pytest.raises(ValueError, match="dot-segment"): + path_template(template, **kwargs) From fbc502dc075d7a0f6890317fc59203ca51d644a7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:58:36 +0000 Subject: [PATCH 04/21] feat: add PRM discovery to MCP gateway endpoint --- .stats.yml | 4 ++-- src/keycardai_api/resources/zones/zones.py | 10 ++++++++++ src/keycardai_api/types/zone.py | 3 +++ src/keycardai_api/types/zone_update_params.py | 6 ++++++ tests/api_resources/test_zones.py | 2 ++ 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0f71c04..d64d05b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-2052af02ac981a44897f047d65dcc9e7a7643c8b2168dd73969ef94ec3d56948.yml -openapi_spec_hash: ed47ef3c3f1aed86c2cb1ff009d60483 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-03f7de343b8ffa831ef87a5c388a5ae56dec933807487c3fdf3d0748214d347e.yml +openapi_spec_hash: 125d9774561f361cbb4c83e143706895 config_hash: 8fdc6a9c1185417459f79052b1222ff0 diff --git a/src/keycardai_api/resources/zones/zones.py b/src/keycardai_api/resources/zones/zones.py index 1c00a24..06cf968 100644 --- a/src/keycardai_api/resources/zones/zones.py +++ b/src/keycardai_api/resources/zones/zones.py @@ -336,6 +336,7 @@ def update( zone_id: str, *, default_mcp_gateway_application_id: Optional[str] | Omit = omit, + default_resource_id: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, encryption_key: Optional[zone_update_params.EncryptionKey] | Omit = omit, login_flow: Optional[Literal["default", "identifier_first"]] | Omit = omit, @@ -357,6 +358,9 @@ def update( default_mcp_gateway_application_id: Application ID configured as the default MCP Gateway for the zone (set to null to unset) + default_resource_id: Resource ID to configure as the default resource for the zone (set to null to + unset) + description: Human-readable description encryption_key: AWS KMS configuration for zone encryption update (set to null to remove @@ -390,6 +394,7 @@ def update( body=maybe_transform( { "default_mcp_gateway_application_id": default_mcp_gateway_application_id, + "default_resource_id": default_resource_id, "description": description, "encryption_key": encryption_key, "login_flow": login_flow, @@ -698,6 +703,7 @@ async def update( zone_id: str, *, default_mcp_gateway_application_id: Optional[str] | Omit = omit, + default_resource_id: Optional[str] | Omit = omit, description: Optional[str] | Omit = omit, encryption_key: Optional[zone_update_params.EncryptionKey] | Omit = omit, login_flow: Optional[Literal["default", "identifier_first"]] | Omit = omit, @@ -719,6 +725,9 @@ async def update( default_mcp_gateway_application_id: Application ID configured as the default MCP Gateway for the zone (set to null to unset) + default_resource_id: Resource ID to configure as the default resource for the zone (set to null to + unset) + description: Human-readable description encryption_key: AWS KMS configuration for zone encryption update (set to null to remove @@ -752,6 +761,7 @@ async def update( body=await async_maybe_transform( { "default_mcp_gateway_application_id": default_mcp_gateway_application_id, + "default_resource_id": default_resource_id, "description": description, "encryption_key": encryption_key, "login_flow": login_flow, diff --git a/src/keycardai_api/types/zone.py b/src/keycardai_api/types/zone.py index cf3849d..95e1c85 100644 --- a/src/keycardai_api/types/zone.py +++ b/src/keycardai_api/types/zone.py @@ -94,6 +94,9 @@ class Zone(BaseModel): default_mcp_gateway_application_id: Optional[str] = None """Application ID configured as the default MCP Gateway for the zone""" + default_resource_id: Optional[str] = None + """Resource ID configured as the default resource for the zone""" + description: Optional[str] = None """Human-readable description""" diff --git a/src/keycardai_api/types/zone_update_params.py b/src/keycardai_api/types/zone_update_params.py index 14abf0a..352d679 100644 --- a/src/keycardai_api/types/zone_update_params.py +++ b/src/keycardai_api/types/zone_update_params.py @@ -15,6 +15,12 @@ class ZoneUpdateParams(TypedDict, total=False): to unset) """ + default_resource_id: Optional[str] + """ + Resource ID to configure as the default resource for the zone (set to null to + unset) + """ + description: Optional[str] """Human-readable description""" diff --git a/tests/api_resources/test_zones.py b/tests/api_resources/test_zones.py index e9a1036..c39b354 100644 --- a/tests/api_resources/test_zones.py +++ b/tests/api_resources/test_zones.py @@ -141,6 +141,7 @@ def test_method_update_with_all_params(self, client: KeycardAPI) -> None: zone = client.zones.update( zone_id="zoneId", default_mcp_gateway_application_id="default_mcp_gateway_application_id", + default_resource_id="default_resource_id", description="description", encryption_key={ "arn": "x", @@ -403,6 +404,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncKeycardAPI zone = await async_client.zones.update( zone_id="zoneId", default_mcp_gateway_application_id="default_mcp_gateway_application_id", + default_resource_id="default_resource_id", description="description", encryption_key={ "arn": "x", From 24f9a15a6031769c2a5912a3eda480fb82fc17b1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:42:25 +0000 Subject: [PATCH 05/21] chore(internal): update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 95ceb18..3824f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .prism.log +.stdy.log _dev __pycache__ From bc52e3f59616d0754b7f0bc30374fac7f357f63b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:19:01 +0000 Subject: [PATCH 06/21] chore(ci): skip lint on metadata-only changes Note that we still want to run tests, as these depend on the metadata. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7518d9c..92182d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/keycard-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - uses: actions/checkout@v6 @@ -35,7 +35,7 @@ jobs: run: ./scripts/lint build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') timeout-minutes: 10 name: build permissions: From bfac76da47db6264d00d9df73c387a8e5e175840 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 08:26:54 +0000 Subject: [PATCH 07/21] feat: add owner_type and enforce protection for platform-owned versions (ACC-29) --- .stats.yml | 4 +- src/keycardai_api/types/zones/__init__.py | 2 +- src/keycardai_api/types/zones/attestation.py | 32 ------------ .../types/zones/attestation_statement.py | 51 +++++++++++++++++++ .../types/zones/policies/policy_version.py | 8 +++ .../zones/policy_sets/policy_set_version.py | 22 +++++--- 6 files changed, 76 insertions(+), 43 deletions(-) delete mode 100644 src/keycardai_api/types/zones/attestation.py create mode 100644 src/keycardai_api/types/zones/attestation_statement.py diff --git a/.stats.yml b/.stats.yml index d64d05b..60fe154 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-03f7de343b8ffa831ef87a5c388a5ae56dec933807487c3fdf3d0748214d347e.yml -openapi_spec_hash: 125d9774561f361cbb4c83e143706895 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-ff839635f0b0cd1d66e886c2c67387c078bf0eb5599376c45af77fd1c7b161fa.yml +openapi_spec_hash: c8b5fdab443323fe491f196a855b4f1d config_hash: 8fdc6a9c1185417459f79052b1222ff0 diff --git a/src/keycardai_api/types/zones/__init__.py b/src/keycardai_api/types/zones/__init__.py index e32d0c2..283dad0 100644 --- a/src/keycardai_api/types/zones/__init__.py +++ b/src/keycardai_api/types/zones/__init__.py @@ -19,7 +19,6 @@ from .public_key import PublicKey as PublicKey from .user_agent import UserAgent as UserAgent from .application import Application as Application -from .attestation import Attestation as Attestation from .base_fields import BaseFields as BaseFields from .zone_member import ZoneMember as ZoneMember from .metadata_param import MetadataParam as MetadataParam @@ -44,6 +43,7 @@ from .secret_create_params import SecretCreateParams as SecretCreateParams from .secret_list_response import SecretListResponse as SecretListResponse from .secret_update_params import SecretUpdateParams as SecretUpdateParams +from .attestation_statement import AttestationStatement as AttestationStatement from .metadata_update_param import MetadataUpdateParam as MetadataUpdateParam from .session_list_response import SessionListResponse as SessionListResponse from .session_update_params import SessionUpdateParams as SessionUpdateParams diff --git a/src/keycardai_api/types/zones/attestation.py b/src/keycardai_api/types/zones/attestation.py deleted file mode 100644 index cf0c50a..0000000 --- a/src/keycardai_api/types/zones/attestation.py +++ /dev/null @@ -1,32 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from ..._models import BaseModel - -__all__ = ["Attestation"] - - -class Attestation(BaseModel): - """JWS Flattened JSON Serialization (RFC 7515 §7.2.2) of a policy set attestation. - - The protected header carries the signing algorithm and key identifier; the payload is a base64url-encoded AttestationStatement canonicalized per RFC 8785 (JCS). Verify using the zone JWKS endpoint (RFC 7517). Currently signed with RS256; future zone key types (e.g. EdDSA) will be indicated by the "alg" header — no envelope changes required. - """ - - payload: str - """Base64url-encoded AttestationStatement (RFC 7515 §3). - - Decode to inspect attestation content. The RFC 8785 canonical form of the - decoded JSON is the JWS Signing Input alongside the protected header. - """ - - protected: str - """Base64url-encoded JWS protected header (RFC 7515 §4). - - Contains at minimum "alg" (signing algorithm — currently RS256, will migrate to - EdDSA) and "kid" (signing key identifier resolvable via the zone JWKS endpoint). - """ - - signature: str - """ - Base64url-encoded digital signature computed over the JWS Signing Input - (ASCII(protected) || '.' || payload) per RFC 7515 §5.1. - """ diff --git a/src/keycardai_api/types/zones/attestation_statement.py b/src/keycardai_api/types/zones/attestation_statement.py new file mode 100644 index 0000000..c83758a --- /dev/null +++ b/src/keycardai_api/types/zones/attestation_statement.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AttestationStatement"] + + +class AttestationStatement(BaseModel): + """Decoded content of an Attestation JWS payload. + + Describes the exact policy set version composition at attestation time. This schema defines what consumers see after base64url-decoding the Attestation.payload field. + """ + + attested_at: datetime + + attested_by: str + + key_id: str + """Key ID of the signing key used to produce the attestation signature. + + Matches the "kid" in the JWS protected header. + """ + + manifest_sha: str + """SHA-256 of the policy set version manifest. + + Verifiers MUST check this matches the policy_set_version.manifest_sha to detect + attestation/version mismatches. + """ + + policy_set_id: str + + policy_set_version: int + + status: Literal["created", "re_signed"] + """Event that produced this attestation. + + "created" is the initial attestation at version creation; "re_signed" is a + re-attestation after key rotation (same content, new signature). + """ + + type: Literal["policy_set_attestation"] + """Statement type discriminator""" + + v: Literal[1] + """Statement schema version""" + + zone_id: str diff --git a/src/keycardai_api/types/zones/policies/policy_version.py b/src/keycardai_api/types/zones/policies/policy_version.py index 9306ce5..d4c1c06 100644 --- a/src/keycardai_api/types/zones/policies/policy_version.py +++ b/src/keycardai_api/types/zones/policies/policy_version.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from ...._models import BaseModel @@ -15,6 +16,13 @@ class PolicyVersion(BaseModel): created_by: str + owner_type: Literal["platform", "customer"] + """Who manages this policy version: + + - `"platform"` — managed by the Keycard platform (system policy versions). + - `"customer"` — managed by the tenant (custom policy versions). + """ + policy_id: str schema_version: str diff --git a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py index e0cfc71..5b89efc 100644 --- a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py +++ b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py @@ -2,10 +2,11 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from ...._models import BaseModel -from ..attestation import Attestation from ..policy_set_manifest import PolicySetManifest +from ..attestation_statement import AttestationStatement __all__ = ["PolicySetVersion"] @@ -22,6 +23,13 @@ class PolicySetVersion(BaseModel): manifest_sha: str """Hex-encoded SHA-256 of the canonicalized manifest""" + owner_type: Literal["platform", "customer"] + """Who manages this policy set version: + + - `"platform"` — managed by the Keycard platform (system policy set versions). + - `"customer"` — managed by the tenant (custom policy set versions). + """ + policy_set_id: str schema_version: str @@ -39,12 +47,10 @@ class PolicySetVersion(BaseModel): archived_by: Optional[str] = None - attestation: Optional[Attestation] = None - """JWS Flattened JSON Serialization (RFC 7515 §7.2.2) of a policy set attestation. + attestation: Optional[AttestationStatement] = None + """Decoded content of an Attestation JWS payload. - The protected header carries the signing algorithm and key identifier; the - payload is a base64url-encoded AttestationStatement canonicalized per RFC 8785 - (JCS). Verify using the zone JWKS endpoint (RFC 7517). Currently signed with - RS256; future zone key types (e.g. EdDSA) will be indicated by the "alg" header - — no envelope changes required. + Describes the exact policy set version composition at attestation time. This + schema defines what consumers see after base64url-decoding the + Attestation.payload field. """ From ac47cc20c48168e226a78d5402d7f8e745881bb8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:23:15 +0000 Subject: [PATCH 08/21] feat: Support for user identifier and provider user identifier claim --- .stats.yml | 4 ++-- .../types/zones/policies/policy_version.py | 8 -------- .../zones/policy_sets/policy_set_version.py | 8 -------- src/keycardai_api/types/zones/provider.py | 6 ++++++ .../types/zones/provider_create_params.py | 6 ++++++ .../types/zones/provider_update_params.py | 7 +++++++ src/keycardai_api/types/zones/user.py | 7 +++++++ tests/api_resources/zones/test_providers.py | 20 +++++++++++++++---- 8 files changed, 44 insertions(+), 22 deletions(-) diff --git a/.stats.yml b/.stats.yml index 60fe154..9496bf7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-ff839635f0b0cd1d66e886c2c67387c078bf0eb5599376c45af77fd1c7b161fa.yml -openapi_spec_hash: c8b5fdab443323fe491f196a855b4f1d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-d42a190ff94ffeb02e41ff81d5a4516d8b0398154547f8a31c22ba7629a03ed1.yml +openapi_spec_hash: 6dfffa6f0eeb6e0538fb58716e9894b0 config_hash: 8fdc6a9c1185417459f79052b1222ff0 diff --git a/src/keycardai_api/types/zones/policies/policy_version.py b/src/keycardai_api/types/zones/policies/policy_version.py index d4c1c06..9306ce5 100644 --- a/src/keycardai_api/types/zones/policies/policy_version.py +++ b/src/keycardai_api/types/zones/policies/policy_version.py @@ -2,7 +2,6 @@ from typing import Optional from datetime import datetime -from typing_extensions import Literal from ...._models import BaseModel @@ -16,13 +15,6 @@ class PolicyVersion(BaseModel): created_by: str - owner_type: Literal["platform", "customer"] - """Who manages this policy version: - - - `"platform"` — managed by the Keycard platform (system policy versions). - - `"customer"` — managed by the tenant (custom policy versions). - """ - policy_id: str schema_version: str diff --git a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py index 5b89efc..f872df3 100644 --- a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py +++ b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py @@ -2,7 +2,6 @@ from typing import Optional from datetime import datetime -from typing_extensions import Literal from ...._models import BaseModel from ..policy_set_manifest import PolicySetManifest @@ -23,13 +22,6 @@ class PolicySetVersion(BaseModel): manifest_sha: str """Hex-encoded SHA-256 of the canonicalized manifest""" - owner_type: Literal["platform", "customer"] - """Who manages this policy set version: - - - `"platform"` — managed by the Keycard platform (system policy set versions). - - `"customer"` — managed by the tenant (custom policy set versions). - """ - policy_set_id: str schema_version: str diff --git a/src/keycardai_api/types/zones/provider.py b/src/keycardai_api/types/zones/provider.py index 2f65195..f7d1c0a 100644 --- a/src/keycardai_api/types/zones/provider.py +++ b/src/keycardai_api/types/zones/provider.py @@ -65,6 +65,12 @@ class ProtocolsOauth2(BaseModel): class ProtocolsOpenid(BaseModel): """OpenID Connect protocol configuration""" + user_identifier_claim: Optional[str] = None + """ + Name of a top-level string claim in this provider's ID Token to use as the user + identifier on user creation. When not set, the user's Keycard ID is used. + """ + userinfo_endpoint: Optional[str] = None diff --git a/src/keycardai_api/types/zones/provider_create_params.py b/src/keycardai_api/types/zones/provider_create_params.py index 4a4d37e..abb5ec2 100644 --- a/src/keycardai_api/types/zones/provider_create_params.py +++ b/src/keycardai_api/types/zones/provider_create_params.py @@ -93,6 +93,12 @@ class ProtocolsOauth2(TypedDict, total=False): class ProtocolsOpenid(TypedDict, total=False): """OpenID Connect protocol configuration for provider creation""" + user_identifier_claim: str + """ + Name of a top-level string claim in this provider's ID Token to use as the user + identifier on user creation. When not set, the user's Keycard ID is used. + """ + userinfo_endpoint: str diff --git a/src/keycardai_api/types/zones/provider_update_params.py b/src/keycardai_api/types/zones/provider_update_params.py index fe393c2..8834000 100644 --- a/src/keycardai_api/types/zones/provider_update_params.py +++ b/src/keycardai_api/types/zones/provider_update_params.py @@ -98,6 +98,13 @@ class ProtocolsOauth2(TypedDict, total=False): class ProtocolsOpenid(TypedDict, total=False): """OpenID Connect protocol configuration. Set to null to remove all OpenID config.""" + user_identifier_claim: Optional[str] + """ + Name of a top-level string claim in this provider's ID Token to use as the user + identifier on user creation. Set to null to revert to default. Changing this + value does not affect existing users. + """ + userinfo_endpoint: Optional[str] diff --git a/src/keycardai_api/types/zones/user.py b/src/keycardai_api/types/zones/user.py index 8155106..fb3cf04 100644 --- a/src/keycardai_api/types/zones/user.py +++ b/src/keycardai_api/types/zones/user.py @@ -23,6 +23,13 @@ class User(BaseModel): email_verified: bool """Whether the email address has been verified""" + identifier: str + """Zone-scoped user identifier. + + Defaults to the user's Keycard ID. When the provider has user_identifier_claim + configured, the value is set from that claim at user creation time. + """ + organization_id: str """Organization that owns this user""" diff --git a/tests/api_resources/zones/test_providers.py b/tests/api_resources/zones/test_providers.py index b4ec8d1..222ed03 100644 --- a/tests/api_resources/zones/test_providers.py +++ b/tests/api_resources/zones/test_providers.py @@ -57,7 +57,10 @@ def test_method_create_with_all_params(self, client: KeycardAPI) -> None: "token_endpoint": "https://example.com", "token_response_access_token_pointer": "token_response_access_token_pointer", }, - "openid": {"userinfo_endpoint": "https://example.com"}, + "openid": { + "user_identifier_claim": "user_identifier_claim", + "userinfo_endpoint": "https://example.com", + }, }, ) assert_matches_type(Provider, provider, path=["response"]) @@ -191,7 +194,10 @@ def test_method_update_with_all_params(self, client: KeycardAPI) -> None: "token_endpoint": "https://example.com", "token_response_access_token_pointer": "token_response_access_token_pointer", }, - "openid": {"userinfo_endpoint": "https://example.com"}, + "openid": { + "user_identifier_claim": "user_identifier_claim", + "userinfo_endpoint": "https://example.com", + }, }, ) assert_matches_type(Provider, provider, path=["response"]) @@ -392,7 +398,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncKeycardAPI "token_endpoint": "https://example.com", "token_response_access_token_pointer": "token_response_access_token_pointer", }, - "openid": {"userinfo_endpoint": "https://example.com"}, + "openid": { + "user_identifier_claim": "user_identifier_claim", + "userinfo_endpoint": "https://example.com", + }, }, ) assert_matches_type(Provider, provider, path=["response"]) @@ -526,7 +535,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncKeycardAPI "token_endpoint": "https://example.com", "token_response_access_token_pointer": "token_response_access_token_pointer", }, - "openid": {"userinfo_endpoint": "https://example.com"}, + "openid": { + "user_identifier_claim": "user_identifier_claim", + "userinfo_endpoint": "https://example.com", + }, }, ) assert_matches_type(Provider, provider, path=["response"]) From 617099df6ed00538530a5f4ed285130dd5ac13c7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:28:11 +0000 Subject: [PATCH 09/21] feat(internal): implement indices array format for query and form serialization --- src/keycardai_api/_qs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/keycardai_api/_qs.py b/src/keycardai_api/_qs.py index ada6fd3..de8c99b 100644 --- a/src/keycardai_api/_qs.py +++ b/src/keycardai_api/_qs.py @@ -101,7 +101,10 @@ def _stringify_item( items.extend(self._stringify_item(key, item, opts)) return items elif array_format == "indices": - raise NotImplementedError("The array indices format is not supported yet") + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items elif array_format == "brackets": items = [] key = key + "[]" From 56e2df57a062c2cd6824053868034b336b81c357 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:44:44 +0000 Subject: [PATCH 10/21] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9496bf7..c87b3ac 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-d42a190ff94ffeb02e41ff81d5a4516d8b0398154547f8a31c22ba7629a03ed1.yml -openapi_spec_hash: 6dfffa6f0eeb6e0538fb58716e9894b0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-c753b5846734341b7c92262feb1f7098470c8c0f02a8d79e93a121898a128242.yml +openapi_spec_hash: fba09085e254f03512e5201d23b00669 config_hash: 8fdc6a9c1185417459f79052b1222ff0 From f9b2707b0649678407423d2f5b3007a6d7bcc2e6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:33:12 +0000 Subject: [PATCH 11/21] feat: add email search to list organization identities endpoint --- .stats.yml | 4 ++-- .../resources/organizations/organizations.py | 8 ++++++++ .../types/organization_list_identities_params.py | 3 +++ src/keycardai_api/types/zones/policies/policy_version.py | 8 ++++++++ .../types/zones/policy_sets/policy_set_version.py | 8 ++++++++ tests/api_resources/test_organizations.py | 2 ++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c87b3ac..36a3199 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-c753b5846734341b7c92262feb1f7098470c8c0f02a8d79e93a121898a128242.yml -openapi_spec_hash: fba09085e254f03512e5201d23b00669 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-d1ec2be127ae8cd75ee4a4656da915596ef337ab2d69c6d0aef1c7f8eb510bc6.yml +openapi_spec_hash: 91893395f0df489c4d31b69581b6d789 config_hash: 8fdc6a9c1185417459f79052b1222ff0 diff --git a/src/keycardai_api/resources/organizations/organizations.py b/src/keycardai_api/resources/organizations/organizations.py index bf6ec16..d19b8f8 100644 --- a/src/keycardai_api/resources/organizations/organizations.py +++ b/src/keycardai_api/resources/organizations/organizations.py @@ -329,6 +329,7 @@ def list_identities( before: str | Omit = omit, expand: List[Literal["permissions"]] | Omit = omit, limit: int | Omit = omit, + query_email: str | Omit = omit, role: OrganizationRole | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -353,6 +354,8 @@ def list_identities( limit: Maximum number of identities to return + query_email: Search identities by email substring (case-insensitive) + role: Filter identities by role extra_headers: Send extra headers @@ -379,6 +382,7 @@ def list_identities( "before": before, "expand": expand, "limit": limit, + "query_email": query_email, "role": role, }, organization_list_identities_params.OrganizationListIdentitiesParams, @@ -711,6 +715,7 @@ async def list_identities( before: str | Omit = omit, expand: List[Literal["permissions"]] | Omit = omit, limit: int | Omit = omit, + query_email: str | Omit = omit, role: OrganizationRole | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -735,6 +740,8 @@ async def list_identities( limit: Maximum number of identities to return + query_email: Search identities by email substring (case-insensitive) + role: Filter identities by role extra_headers: Send extra headers @@ -761,6 +768,7 @@ async def list_identities( "before": before, "expand": expand, "limit": limit, + "query_email": query_email, "role": role, }, organization_list_identities_params.OrganizationListIdentitiesParams, diff --git a/src/keycardai_api/types/organization_list_identities_params.py b/src/keycardai_api/types/organization_list_identities_params.py index 80f52b6..59d3229 100644 --- a/src/keycardai_api/types/organization_list_identities_params.py +++ b/src/keycardai_api/types/organization_list_identities_params.py @@ -28,6 +28,9 @@ class OrganizationListIdentitiesParams(TypedDict, total=False): limit: int """Maximum number of identities to return""" + query_email: Annotated[str, PropertyInfo(alias="query[email]")] + """Search identities by email substring (case-insensitive)""" + role: OrganizationRole """Filter identities by role""" diff --git a/src/keycardai_api/types/zones/policies/policy_version.py b/src/keycardai_api/types/zones/policies/policy_version.py index 9306ce5..d4c1c06 100644 --- a/src/keycardai_api/types/zones/policies/policy_version.py +++ b/src/keycardai_api/types/zones/policies/policy_version.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from ...._models import BaseModel @@ -15,6 +16,13 @@ class PolicyVersion(BaseModel): created_by: str + owner_type: Literal["platform", "customer"] + """Who manages this policy version: + + - `"platform"` — managed by the Keycard platform (system policy versions). + - `"customer"` — managed by the tenant (custom policy versions). + """ + policy_id: str schema_version: str diff --git a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py index f872df3..5b89efc 100644 --- a/src/keycardai_api/types/zones/policy_sets/policy_set_version.py +++ b/src/keycardai_api/types/zones/policy_sets/policy_set_version.py @@ -2,6 +2,7 @@ from typing import Optional from datetime import datetime +from typing_extensions import Literal from ...._models import BaseModel from ..policy_set_manifest import PolicySetManifest @@ -22,6 +23,13 @@ class PolicySetVersion(BaseModel): manifest_sha: str """Hex-encoded SHA-256 of the canonicalized manifest""" + owner_type: Literal["platform", "customer"] + """Who manages this policy set version: + + - `"platform"` — managed by the Keycard platform (system policy set versions). + - `"customer"` — managed by the tenant (custom policy set versions). + """ + policy_set_id: str schema_version: str diff --git a/tests/api_resources/test_organizations.py b/tests/api_resources/test_organizations.py index efc7af8..d60ac2a 100644 --- a/tests/api_resources/test_organizations.py +++ b/tests/api_resources/test_organizations.py @@ -272,6 +272,7 @@ def test_method_list_identities_with_all_params(self, client: KeycardAPI) -> Non before="x", expand=["permissions"], limit=1, + query_email="x", role="org_admin", x_client_request_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) @@ -619,6 +620,7 @@ async def test_method_list_identities_with_all_params(self, async_client: AsyncK before="x", expand=["permissions"], limit=1, + query_email="x", role="org_admin", x_client_request_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) From 25b0ff97f4053af31f8dd4f6bca04ec3bffd8930 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:03:54 +0000 Subject: [PATCH 12/21] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 36a3199..e3398a6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-d1ec2be127ae8cd75ee4a4656da915596ef337ab2d69c6d0aef1c7f8eb510bc6.yml -openapi_spec_hash: 91893395f0df489c4d31b69581b6d789 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-be51d18f087d1fa1e5e878dbd273f616c42e9e6e40b8a5456bb7a1e69ae00342.yml +openapi_spec_hash: a532797e097afb1f244274b2652021c9 config_hash: 8fdc6a9c1185417459f79052b1222ff0 From 6c023dcca457875bf74ba43dad0e71ddd14f00dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:33:16 +0000 Subject: [PATCH 13/21] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e3398a6..95917d7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-be51d18f087d1fa1e5e878dbd273f616c42e9e6e40b8a5456bb7a1e69ae00342.yml -openapi_spec_hash: a532797e097afb1f244274b2652021c9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-aee6e66de0d39a7d01389110b109928c0bfc11cae3be6d4f549196e1f56606e0.yml +openapi_spec_hash: 0d50174755dcf2dc99e2b0001100a927 config_hash: 8fdc6a9c1185417459f79052b1222ff0 From bc985cb90a7962956fb65e8e21f5d39fb27a22b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:45:23 +0000 Subject: [PATCH 14/21] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 95917d7..e258171 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-aee6e66de0d39a7d01389110b109928c0bfc11cae3be6d4f549196e1f56606e0.yml -openapi_spec_hash: 0d50174755dcf2dc99e2b0001100a927 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-a52e80c8dfdc95e54ac6d7d6da7927f603a48831a6b79865774c7f157520e301.yml +openapi_spec_hash: 1e68dfb30b1f6d6a8a0e909d6e4299a5 config_hash: 8fdc6a9c1185417459f79052b1222ff0 From 52243ca92a5c3420d88023368c22d5b103c287bf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:11:11 +0000 Subject: [PATCH 15/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index e258171..3aac3f3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-a52e80c8dfdc95e54ac6d7d6da7927f603a48831a6b79865774c7f157520e301.yml openapi_spec_hash: 1e68dfb30b1f6d6a8a0e909d6e4299a5 -config_hash: 8fdc6a9c1185417459f79052b1222ff0 +config_hash: 917f16944c4165d8c17e84b201ba8a89 From 8b7846a1c1e955b60c594865a1e1038aa878bc5b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:11:52 +0000 Subject: [PATCH 16/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 3aac3f3..d2e5507 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-a52e80c8dfdc95e54ac6d7d6da7927f603a48831a6b79865774c7f157520e301.yml openapi_spec_hash: 1e68dfb30b1f6d6a8a0e909d6e4299a5 -config_hash: 917f16944c4165d8c17e84b201ba8a89 +config_hash: fab2044922865fa04fb03f5f19e195a0 From 0aa4b0700df84b7f83f60edf1dafa6fd09d44feb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:19:44 +0000 Subject: [PATCH 17/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d2e5507..3aac3f3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-a52e80c8dfdc95e54ac6d7d6da7927f603a48831a6b79865774c7f157520e301.yml openapi_spec_hash: 1e68dfb30b1f6d6a8a0e909d6e4299a5 -config_hash: fab2044922865fa04fb03f5f19e195a0 +config_hash: 917f16944c4165d8c17e84b201ba8a89 From f9f9c4be4dee14d7aa495844a4bc196241b904d9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:20:42 +0000 Subject: [PATCH 18/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 3aac3f3..d2e5507 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-a52e80c8dfdc95e54ac6d7d6da7927f603a48831a6b79865774c7f157520e301.yml openapi_spec_hash: 1e68dfb30b1f6d6a8a0e909d6e4299a5 -config_hash: 917f16944c4165d8c17e84b201ba8a89 +config_hash: fab2044922865fa04fb03f5f19e195a0 From d91f00f0d69e2c011148c6b859257c1f716ab7f4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:38:02 +0000 Subject: [PATCH 19/21] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d2e5507..dedae5d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-a52e80c8dfdc95e54ac6d7d6da7927f603a48831a6b79865774c7f157520e301.yml -openapi_spec_hash: 1e68dfb30b1f6d6a8a0e909d6e4299a5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-66564a738b57bf537ef9f25f8865d8e115e07927c967bfd10b8c657ae501848f.yml +openapi_spec_hash: 92f837f627811e1ed5c36ba5d05a4a30 config_hash: fab2044922865fa04fb03f5f19e195a0 From d4c4d1ba4d76d709e6067d36f1848d293d227a81 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:54:05 +0000 Subject: [PATCH 20/21] feat: improved identities pagination --- .stats.yml | 4 +- .../resources/organizations/invitations.py | 18 +++-- .../resources/organizations/organizations.py | 72 ++++++++++++------- .../service_accounts/credentials.py | 36 ++++++---- .../service_accounts/service_accounts.py | 36 ++++++---- .../resources/organizations/sso_connection.py | 18 +++-- .../resources/organizations/users.py | 36 ++++++---- .../organization_list_identities_params.py | 9 ++- .../organization_list_identities_response.py | 21 +++++- .../types/organization_list_params.py | 9 ++- .../types/organization_list_roles_params.py | 9 ++- .../types/organization_retrieve_params.py | 9 ++- .../organizations/invitation_list_params.py | 9 ++- .../service_account_list_params.py | 9 ++- .../service_account_retrieve_params.py | 9 ++- .../credential_list_params.py | 9 ++- .../credential_retrieve_params.py | 9 ++- .../sso_connection_retrieve_params.py | 9 ++- .../types/organizations/user_list_params.py | 9 ++- .../organizations/user_retrieve_params.py | 9 ++- 20 files changed, 238 insertions(+), 111 deletions(-) diff --git a/.stats.yml b/.stats.yml index dedae5d..f7292bc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 106 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-66564a738b57bf537ef9f25f8865d8e115e07927c967bfd10b8c657ae501848f.yml -openapi_spec_hash: 92f837f627811e1ed5c36ba5d05a4a30 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/keycard%2Fkeycard-api-f935374f57248d24e5f8a2393ce0730503bc466f7c51e93b6d3225d5b9317baf.yml +openapi_spec_hash: de0d11f4dcc9810bcb0d6bb8882e21a4 config_hash: fab2044922865fa04fb03f5f19e195a0 diff --git a/src/keycardai_api/resources/organizations/invitations.py b/src/keycardai_api/resources/organizations/invitations.py index ca9f974..ab0ccce 100644 --- a/src/keycardai_api/resources/organizations/invitations.py +++ b/src/keycardai_api/resources/organizations/invitations.py @@ -102,7 +102,7 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -122,8 +122,11 @@ def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of invitations to return @@ -282,7 +285,7 @@ async def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -302,8 +305,11 @@ async def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of invitations to return diff --git a/src/keycardai_api/resources/organizations/organizations.py b/src/keycardai_api/resources/organizations/organizations.py index d19b8f8..49df542 100644 --- a/src/keycardai_api/resources/organizations/organizations.py +++ b/src/keycardai_api/resources/organizations/organizations.py @@ -145,7 +145,7 @@ def retrieve( self, organization_id: str, *, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -160,8 +160,11 @@ def retrieve( Args: organization_id: Organization ID or label identifier - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -232,7 +235,7 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -250,8 +253,11 @@ def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of organizations to return @@ -327,7 +333,7 @@ def list_identities( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, query_email: str | Omit = omit, role: OrganizationRole | Omit = omit, @@ -349,8 +355,11 @@ def list_identities( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of identities to return @@ -395,7 +404,7 @@ def list_roles( self, organization_id: str, *, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, scope: RoleScope | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -420,8 +429,11 @@ def list_roles( Args: organization_id: Organization ID or label identifier - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. scope: Filter roles by scope (organization or zone level) @@ -529,7 +541,7 @@ async def retrieve( self, organization_id: str, *, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -544,8 +556,11 @@ async def retrieve( Args: organization_id: Organization ID or label identifier - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -618,7 +633,7 @@ async def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -636,8 +651,11 @@ async def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of organizations to return @@ -713,7 +731,7 @@ async def list_identities( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, query_email: str | Omit = omit, role: OrganizationRole | Omit = omit, @@ -735,8 +753,11 @@ async def list_identities( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of identities to return @@ -781,7 +802,7 @@ async def list_roles( self, organization_id: str, *, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, scope: RoleScope | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -806,8 +827,11 @@ async def list_roles( Args: organization_id: Organization ID or label identifier - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. scope: Filter roles by scope (organization or zone level) diff --git a/src/keycardai_api/resources/organizations/service_accounts/credentials.py b/src/keycardai_api/resources/organizations/service_accounts/credentials.py index d316575..e74e94a 100644 --- a/src/keycardai_api/resources/organizations/service_accounts/credentials.py +++ b/src/keycardai_api/resources/organizations/service_accounts/credentials.py @@ -116,7 +116,7 @@ def retrieve( *, organization_id: str, service_account_id: str, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -135,8 +135,11 @@ def retrieve( credential_id: Identifier for API resources. A 26-char nanoid (URL/DNS safe). - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -242,7 +245,7 @@ def list( organization_id: str, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -264,8 +267,11 @@ def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of credentials to return @@ -445,7 +451,7 @@ async def retrieve( *, organization_id: str, service_account_id: str, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -464,8 +470,11 @@ async def retrieve( credential_id: Identifier for API resources. A 26-char nanoid (URL/DNS safe). - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -573,7 +582,7 @@ async def list( organization_id: str, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -595,8 +604,11 @@ async def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of credentials to return diff --git a/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py b/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py index 2e6916b..68715b3 100644 --- a/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py +++ b/src/keycardai_api/resources/organizations/service_accounts/service_accounts.py @@ -117,7 +117,7 @@ def retrieve( service_account_id: str, *, organization_id: str, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -134,8 +134,11 @@ def retrieve( service_account_id: Identifier for API resources. A 26-char nanoid (URL/DNS safe). - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -231,7 +234,7 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -251,8 +254,11 @@ def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of service accounts to return @@ -414,7 +420,7 @@ async def retrieve( service_account_id: str, *, organization_id: str, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -431,8 +437,11 @@ async def retrieve( service_account_id: Identifier for API resources. A 26-char nanoid (URL/DNS safe). - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -530,7 +539,7 @@ async def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, x_client_request_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -550,8 +559,11 @@ async def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of service accounts to return diff --git a/src/keycardai_api/resources/organizations/sso_connection.py b/src/keycardai_api/resources/organizations/sso_connection.py index 9657687..4a8a44f 100644 --- a/src/keycardai_api/resources/organizations/sso_connection.py +++ b/src/keycardai_api/resources/organizations/sso_connection.py @@ -53,7 +53,7 @@ def retrieve( self, organization_id: str, *, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -68,8 +68,11 @@ def retrieve( Args: organization_id: Organization ID or label identifier - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -273,7 +276,7 @@ async def retrieve( self, organization_id: str, *, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -288,8 +291,11 @@ async def retrieve( Args: organization_id: Organization ID or label identifier - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers diff --git a/src/keycardai_api/resources/organizations/users.py b/src/keycardai_api/resources/organizations/users.py index 8f53d68..9ef4fa1 100644 --- a/src/keycardai_api/resources/organizations/users.py +++ b/src/keycardai_api/resources/organizations/users.py @@ -58,7 +58,7 @@ def retrieve( user_id: str, *, organization_id: str, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -75,8 +75,11 @@ def retrieve( user_id: Identifier for API resources. A 26-char nanoid (URL/DNS safe). - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -168,7 +171,7 @@ def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, role: OrganizationRole | Omit = omit, x_client_request_id: str | Omit = omit, @@ -189,8 +192,11 @@ def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of users to return @@ -299,7 +305,7 @@ async def retrieve( user_id: str, *, organization_id: str, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, x_client_request_id: str | 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. @@ -316,8 +322,11 @@ async def retrieve( user_id: Identifier for API resources. A 26-char nanoid (URL/DNS safe). - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. extra_headers: Send extra headers @@ -409,7 +418,7 @@ async def list( *, after: str | Omit = omit, before: str | Omit = omit, - expand: List[Literal["permissions"]] | Omit = omit, + expand: List[Literal["permissions", "total_count"]] | Omit = omit, limit: int | Omit = omit, role: OrganizationRole | Omit = omit, x_client_request_id: str | Omit = omit, @@ -430,8 +439,11 @@ async def list( before: Cursor for backward pagination - expand: Fields to expand in the response. Currently supports "permissions" to include - the permissions field with the caller's permissions for the resource. + expand: Fields to expand in the response. Supports "permissions" to include the + permissions field with the caller's permissions for the resource. For list + organization identities only, "total_count" populates pagination.total_count + with the number of identities matching the same filters as the list (excluding + cursor and limit). Other operations ignore expand values they do not use. limit: Maximum number of users to return diff --git a/src/keycardai_api/types/organization_list_identities_params.py b/src/keycardai_api/types/organization_list_identities_params.py index 59d3229..f0bc729 100644 --- a/src/keycardai_api/types/organization_list_identities_params.py +++ b/src/keycardai_api/types/organization_list_identities_params.py @@ -18,11 +18,14 @@ class OrganizationListIdentitiesParams(TypedDict, total=False): before: str """Cursor for backward pagination""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ limit: int diff --git a/src/keycardai_api/types/organization_list_identities_response.py b/src/keycardai_api/types/organization_list_identities_response.py index c990b44..d42fd55 100644 --- a/src/keycardai_api/types/organization_list_identities_response.py +++ b/src/keycardai_api/types/organization_list_identities_response.py @@ -8,7 +8,7 @@ from .page_info_cursor import PageInfoCursor from .organizations.organization_role import OrganizationRole -__all__ = ["OrganizationListIdentitiesResponse", "Item"] +__all__ = ["OrganizationListIdentitiesResponse", "Item", "Pagination"] class Item(BaseModel): @@ -50,6 +50,22 @@ class Item(BaseModel): """ +class Pagination(BaseModel): + """Cursor-based pagination metadata returned alongside a list of results""" + + after_cursor: str + """An opaque cursor used for paginating through a list of results""" + + before_cursor: str + """An opaque cursor used for paginating through a list of results""" + + total_count: Optional[int] = None + """Total number of items across all pages. + + Only present when the request includes ?expand=total_count. + """ + + class OrganizationListIdentitiesResponse(BaseModel): """List of identities (users and invitations) in an organization""" @@ -58,6 +74,9 @@ class OrganizationListIdentitiesResponse(BaseModel): page_info: PageInfoCursor """Pagination information using cursor-based pagination""" + pagination: Pagination + """Cursor-based pagination metadata returned alongside a list of results""" + permissions: Optional[Dict[str, Dict[str, bool]]] = None """ Permissions granted to the authenticated principal for this resource. Only diff --git a/src/keycardai_api/types/organization_list_params.py b/src/keycardai_api/types/organization_list_params.py index b4a9f9d..dd31beb 100644 --- a/src/keycardai_api/types/organization_list_params.py +++ b/src/keycardai_api/types/organization_list_params.py @@ -17,11 +17,14 @@ class OrganizationListParams(TypedDict, total=False): before: str """Cursor for backward pagination""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ limit: int diff --git a/src/keycardai_api/types/organization_list_roles_params.py b/src/keycardai_api/types/organization_list_roles_params.py index ab37c20..99b7a70 100644 --- a/src/keycardai_api/types/organization_list_roles_params.py +++ b/src/keycardai_api/types/organization_list_roles_params.py @@ -12,11 +12,14 @@ class OrganizationListRolesParams(TypedDict, total=False): - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ scope: RoleScope diff --git a/src/keycardai_api/types/organization_retrieve_params.py b/src/keycardai_api/types/organization_retrieve_params.py index 3feac27..1a5d525 100644 --- a/src/keycardai_api/types/organization_retrieve_params.py +++ b/src/keycardai_api/types/organization_retrieve_params.py @@ -11,11 +11,14 @@ class OrganizationRetrieveParams(TypedDict, total=False): - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")] diff --git a/src/keycardai_api/types/organizations/invitation_list_params.py b/src/keycardai_api/types/organizations/invitation_list_params.py index 093ead1..30b5da3 100644 --- a/src/keycardai_api/types/organizations/invitation_list_params.py +++ b/src/keycardai_api/types/organizations/invitation_list_params.py @@ -17,11 +17,14 @@ class InvitationListParams(TypedDict, total=False): before: str """Cursor for backward pagination""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ limit: int diff --git a/src/keycardai_api/types/organizations/service_account_list_params.py b/src/keycardai_api/types/organizations/service_account_list_params.py index e3f9d40..7b32b8f 100644 --- a/src/keycardai_api/types/organizations/service_account_list_params.py +++ b/src/keycardai_api/types/organizations/service_account_list_params.py @@ -17,11 +17,14 @@ class ServiceAccountListParams(TypedDict, total=False): before: str """Cursor for backward pagination""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ limit: int diff --git a/src/keycardai_api/types/organizations/service_account_retrieve_params.py b/src/keycardai_api/types/organizations/service_account_retrieve_params.py index c138d5d..b3ca28b 100644 --- a/src/keycardai_api/types/organizations/service_account_retrieve_params.py +++ b/src/keycardai_api/types/organizations/service_account_retrieve_params.py @@ -14,11 +14,14 @@ class ServiceAccountRetrieveParams(TypedDict, total=False): organization_id: Required[str] """Organization ID or label identifier""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")] diff --git a/src/keycardai_api/types/organizations/service_accounts/credential_list_params.py b/src/keycardai_api/types/organizations/service_accounts/credential_list_params.py index 7fb0efe..7843e64 100644 --- a/src/keycardai_api/types/organizations/service_accounts/credential_list_params.py +++ b/src/keycardai_api/types/organizations/service_accounts/credential_list_params.py @@ -20,11 +20,14 @@ class CredentialListParams(TypedDict, total=False): before: str """Cursor for backward pagination""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ limit: int diff --git a/src/keycardai_api/types/organizations/service_accounts/credential_retrieve_params.py b/src/keycardai_api/types/organizations/service_accounts/credential_retrieve_params.py index 68ed360..e9ebe55 100644 --- a/src/keycardai_api/types/organizations/service_accounts/credential_retrieve_params.py +++ b/src/keycardai_api/types/organizations/service_accounts/credential_retrieve_params.py @@ -17,11 +17,14 @@ class CredentialRetrieveParams(TypedDict, total=False): service_account_id: Required[str] """Identifier for API resources. A 26-char nanoid (URL/DNS safe).""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")] diff --git a/src/keycardai_api/types/organizations/sso_connection_retrieve_params.py b/src/keycardai_api/types/organizations/sso_connection_retrieve_params.py index bacc08e..63c48c6 100644 --- a/src/keycardai_api/types/organizations/sso_connection_retrieve_params.py +++ b/src/keycardai_api/types/organizations/sso_connection_retrieve_params.py @@ -11,11 +11,14 @@ class SSOConnectionRetrieveParams(TypedDict, total=False): - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")] diff --git a/src/keycardai_api/types/organizations/user_list_params.py b/src/keycardai_api/types/organizations/user_list_params.py index 9860ad7..00cd32f 100644 --- a/src/keycardai_api/types/organizations/user_list_params.py +++ b/src/keycardai_api/types/organizations/user_list_params.py @@ -18,11 +18,14 @@ class UserListParams(TypedDict, total=False): before: str """Cursor for backward pagination""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ limit: int diff --git a/src/keycardai_api/types/organizations/user_retrieve_params.py b/src/keycardai_api/types/organizations/user_retrieve_params.py index 9f8ce63..001ce54 100644 --- a/src/keycardai_api/types/organizations/user_retrieve_params.py +++ b/src/keycardai_api/types/organizations/user_retrieve_params.py @@ -14,11 +14,14 @@ class UserRetrieveParams(TypedDict, total=False): organization_id: Required[str] """Organization ID or label identifier""" - expand: List[Literal["permissions"]] + expand: List[Literal["permissions", "total_count"]] """Fields to expand in the response. - Currently supports "permissions" to include the permissions field with the - caller's permissions for the resource. + Supports "permissions" to include the permissions field with the caller's + permissions for the resource. For list organization identities only, + "total_count" populates pagination.total_count with the number of identities + matching the same filters as the list (excluding cursor and limit). Other + operations ignore expand values they do not use. """ x_client_request_id: Annotated[str, PropertyInfo(alias="X-Client-Request-ID")] From 62849f691d984c78cdd3a4d29e52b52ad5ce249a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:54:24 +0000 Subject: [PATCH 21/21] release: 0.4.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ pyproject.toml | 2 +- src/keycardai_api/_version.py | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b7b74c..da59f99 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "0.4.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e6ee0..4c25f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.4.0 (2026-04-03) + +Full Changelog: [v0.3.0...v0.4.0](https://github.com/keycardai/keycard-python/compare/v0.3.0...v0.4.0) + +### Features + +* add email search to list organization identities endpoint ([f9b2707](https://github.com/keycardai/keycard-python/commit/f9b2707b0649678407423d2f5b3007a6d7bcc2e6)) +* add owner_type and enforce protection for platform-owned versions (ACC-29) ([bfac76d](https://github.com/keycardai/keycard-python/commit/bfac76da47db6264d00d9df73c387a8e5e175840)) +* add PRM discovery to MCP gateway endpoint ([fbc502d](https://github.com/keycardai/keycard-python/commit/fbc502dc075d7a0f6890317fc59203ca51d644a7)) +* improved identities pagination ([d4c4d1b](https://github.com/keycardai/keycard-python/commit/d4c4d1ba4d76d709e6067d36f1848d293d227a81)) +* **internal:** implement indices array format for query and form serialization ([617099d](https://github.com/keycardai/keycard-python/commit/617099df6ed00538530a5f4ed285130dd5ac13c7)) +* provide more context for policy schema ([46bea8a](https://github.com/keycardai/keycard-python/commit/46bea8a4493318b0b37904119e057cb7b4e50550)) +* Support for user identifier and provider user identifier claim ([ac47cc2](https://github.com/keycardai/keycard-python/commit/ac47cc20c48168e226a78d5402d7f8e745881bb8)) + + +### Bug Fixes + +* sanitize endpoint path params ([6993e56](https://github.com/keycardai/keycard-python/commit/6993e56d5e9a79713471a0b2b33920c1e2da77d9)) + + +### Chores + +* **ci:** skip lint on metadata-only changes ([bc52e3f](https://github.com/keycardai/keycard-python/commit/bc52e3f59616d0754b7f0bc30374fac7f357f63b)) +* **internal:** update gitignore ([24f9a15](https://github.com/keycardai/keycard-python/commit/24f9a15a6031769c2a5912a3eda480fb82fc17b1)) + ## 0.3.0 (2026-03-16) Full Changelog: [v0.2.0...v0.3.0](https://github.com/keycardai/keycard-python/compare/v0.2.0...v0.3.0) diff --git a/pyproject.toml b/pyproject.toml index 77ffed2..5b59e67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "keycardai_api" -version = "0.3.0" +version = "0.4.0" description = "The official Python library for the keycard-api API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/keycardai_api/_version.py b/src/keycardai_api/_version.py index 239bc4c..6bfff43 100644 --- a/src/keycardai_api/_version.py +++ b/src/keycardai_api/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "keycardai_api" -__version__ = "0.3.0" # x-release-please-version +__version__ = "0.4.0" # x-release-please-version