diff --git a/.castiron.stats.yml b/.castiron.stats.yml index ad96f5a66a..23b33efd22 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,7 +1,7 @@ schema_version: 1 -generation_id: 5f43d20e-1fa7-4356-a5cf-9ba76ebd71ce -openapi_spec_hash: d86a987b0943f67209611629cfe2926f -openapi_transformed_spec_hash: f98b6939664e5171dadad31a12ecf50a -config_hash: 0e042fd083471fc089bcbfe7011a7d01 -codegen_sha: eaa28252a89f885820afa16bbd1a753ec589a3c1 -codegen_hash: e1d1fbd9e1e10bd7f346d72af1f6a726fbf8c6f7ef655552bfc339f2be788c90 +generation_id: 02a4f051-9120-487b-8954-9ea8b97df229 +openapi_spec_hash: 017866de8f67cefec8724f830463cbe2 +openapi_transformed_spec_hash: d7de2a7f65152a983cdba4c6b4a20d76 +config_hash: 3d64bda5a8bbef3ad0572848ae7e87f7 +codegen_sha: ab8360c6a37ccd1f94712f0aa9646349279084b1 +codegen_hash: cd27c533727eae2ee5d4350f07afd15d7de38e89fd3c4c2e35dbda61cd39f5d8 diff --git a/api.md b/api.md index a115e0a0e2..5c1bbf74e9 100644 --- a/api.md +++ b/api.md @@ -1855,6 +1855,22 @@ Methods: - client.admin.organization.projects.certificates.activate(project_id, \*\*params) -> SyncPage[CertificateActivateResponse] - client.admin.organization.projects.certificates.deactivate(project_id, \*\*params) -> SyncPage[CertificateDeactivateResponse] +### ExternalKeys + +Types: + +```python +from openai.types.admin.organization import ExternalKey, ExternalKeyValidateResponse +``` + +Methods: + +- client.admin.organization.external_keys.create(\*\*params) -> ExternalKey +- client.admin.organization.external_keys.retrieve(external_key_id) -> ExternalKey +- client.admin.organization.external_keys.list(\*\*params) -> SyncCursorPage[ExternalKey] +- client.admin.organization.external_keys.delete(external_key_id) -> ExternalKey +- client.admin.organization.external_keys.validate(external_key_id) -> ExternalKeyValidateResponse + # [Responses](src/openai/resources/responses/api.md) # [Live](src/openai/resources/live/api.md) diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml index 5662ced2ba..2b783ff234 100644 --- a/api_reference/openapi.transformed.yml +++ b/api_reference/openapi.transformed.yml @@ -37508,6 +37508,125 @@ paths: "object": "response.input_tokens", "input_tokens": 11 } + /organization/external_keys: + post: + summary: Add an external key config for an organization + description: Add an external key config for an organization + operationId: Addanexternalkeyconfigforanorganization + parameters: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/V1OrganizationExternalKeysPostRequest' + required: true + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalKeyResource' + security: + - AdminApiKeyAuth: [] + get: + summary: List the external key configs registered on the user's organization + description: List the external key configs registered on the user's organization + operationId: Listtheexternalkeyconfigsregisteredontheuser'sorganization + parameters: + - name: limit + in: query + required: false + schema: + type: integer + minimum: 0 + maximum: 100 + - name: order + in: query + required: false + schema: + $ref: '#/components/schemas/OrderEnum' + - name: after + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + - name: purpose + in: query + description: Filter by key purpose. When omitted, returns keys of all purposes. + required: false + schema: + $ref: '#/components/schemas/ExternalKeyPurpose' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ListExternalKeysResource' + security: + - AdminApiKeyAuth: [] + /organization/external_keys/{external_key_id}: + get: + summary: Retrieve an external key registration + description: Retrieve a stored external key registration for the user's organization. + operationId: Retrieveanexternalkeyregistration + parameters: + - name: external_key_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalKeyResource' + security: + - AdminApiKeyAuth: [] + delete: + summary: Delete an external key config for the user's organization + description: Delete an external key config for the user's organization + operationId: Deleteanexternalkeyconfigfortheuser'sorganization + parameters: + - name: external_key_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ExternalKeyResource' + security: + - AdminApiKeyAuth: [] + /organization/external_keys/{external_key_id}/validate: + post: + summary: Validate an external key config for the user's organization + description: Validate an external key config for the user's organization + operationId: Validateanexternalkeyconfigfortheuser'sorganization + parameters: + - name: external_key_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/ValidateExternalKeyResource' + security: + - AdminApiKeyAuth: [] webhooks: agent_environment_expired: post: @@ -110678,6 +110797,236 @@ components: - num_requests title: Web search usage description: Usage details for web search requests that ran while generating the response. + AwsExternalKeyParams: + properties: + name: + type: string + description: A friendly name to identify this config + type: + type: string + enum: + - aws + default: aws + x-stainless-const: true + role_arn: + type: string + pattern: ^arn:aws:iam::\d{12}:role/.+$ + description: The Role ARN that OpenAI will assume to call your KMS + kms_arn: + type: string + description: The Key Management System ARN for the master key you manage + external_id: + type: string + description: The ExternalID that OpenAI will pass in the AssumeRole call for the customer's AWS account. This must be the OpenAI organization ID or an API project ID that belongs to the organization. + purpose: + $ref: '#/components/schemas/ExternalKeyPurpose' + type: object + required: + - name + - type + - role_arn + - kms_arn + - external_id + AzureExternalKeyParams: + properties: + name: + type: string + description: A friendly name to identify this config + type: + type: string + enum: + - azure + default: azure + x-stainless-const: true + tenant_id: + type: string + pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ + description: Your Azure tenant UUID + vault_uri: + type: string + pattern: ^https://.+\.vault\.azure\.net/?$ + description: The URI of the Azure vault containing the master key you manage + key_name: + type: string + maxLength: 127 + pattern: ^org-[0-9a-zA-Z-]*--[0-9a-zA-Z-]*$ + description: The name of the Azure Key Vault master key that you manage + purpose: + $ref: '#/components/schemas/ExternalKeyPurpose' + key_version: + type: string + pattern: ^[0-9a-fA-F]{32}$ + description: An exact Azure Key Vault key version. Required for PSP decryption; not accepted for EKM. + type: object + required: + - name + - type + - tenant_id + - vault_uri + - key_name + ExternalKeyPurpose: + type: string + enum: + - ekm + - psp_decryption + ExternalKeyResource: + properties: + object: + type: string + id: + type: string + type: + type: string + purpose: + $ref: '#/components/schemas/ExternalKeyPurpose' + name: + type: string + api_project_ids: + items: + type: string + type: array + workspace_id: + type: string + created_at: + type: integer + role_arn: + type: string + external_id: + type: string + kms_arn: + type: string + workload_identity_project_number: + type: string + workload_identity_pool_id: + type: string + workload_identity_provider_id: + type: string + audience: + type: string + kms_project_id: + type: string + kms_key_ring_name: + type: string + kms_key_name: + type: string + kms_key_location: + type: string + kms_key_version: + type: string + tenant_id: + type: string + vault_uri: + type: string + key_name: + type: string + key_version: + type: string + type: object + required: + - object + - id + - type + - name + - api_project_ids + - workspace_id + - created_at + GcpExternalKeyParams: + properties: + name: + type: string + description: A friendly name to identify this config + type: + type: string + enum: + - gcp + default: gcp + x-stainless-const: true + workload_identity_project_number: + type: string + pattern: ^[0-9]{1,19}$ + description: The numeric GCP project number where you registered OpenAI's workload identity + workload_identity_pool_id: + type: string + description: The pool containing the Workload Identity provider that you registered for OpenAI + workload_identity_provider_id: + type: string + description: The Workload Identity provider that you registered for OpenAI + kms_project_id: + type: string + description: The string GCP project name where your KMS resides + kms_key_ring_name: + type: string + description: The Key Management System key ring for the master key you manage + kms_key_name: + type: string + description: The name of the Key Management System master key you manage + kms_key_location: + type: string + description: The region where your Key Management System master key is located + audience: + type: string + description: The aud field in the identity token OpenAI will pass in the call to the customer's GCP STS. This must be the OpenAI organization ID or an API project ID that belongs to the organization. + purpose: + $ref: '#/components/schemas/ExternalKeyPurpose' + kms_key_version: + type: string + pattern: ^[1-9][0-9]*$ + description: An exact CryptoKeyVersion number. Required for PSP decryption; not accepted for EKM. + type: object + required: + - name + - type + - workload_identity_project_number + - workload_identity_pool_id + - workload_identity_provider_id + - kms_project_id + - kms_key_ring_name + - kms_key_name + - kms_key_location + - audience + ListExternalKeysResource: + properties: + object: + type: string + enum: + - list + default: list + x-stainless-const: true + data: + items: + $ref: '#/components/schemas/ExternalKeyResource' + type: array + first_id: + anyOf: + - type: string + - type: 'null' + last_id: + anyOf: + - type: string + - type: 'null' + has_more: + type: boolean + type: object + required: + - object + - data + - first_id + - last_id + - has_more + V1OrganizationExternalKeysPostRequest: + discriminator: + propertyName: type + anyOf: + - $ref: '#/components/schemas/AwsExternalKeyParams' + - $ref: '#/components/schemas/GcpExternalKeyParams' + - $ref: '#/components/schemas/AzureExternalKeyParams' + ValidateExternalKeyResource: + properties: + status: + type: string + type: object + required: + - status SessionSpendControlResource: type: object properties: diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json index 6136c8513d..bbc7b41e27 100644 --- a/scripts/castiron/generated-files.json +++ b/scripts/castiron/generated-files.json @@ -3,7 +3,7 @@ "files": { "api.md": { "mode": "100644", - "sha256": "82da84d639cb84106f389b001030b0b668eb71122f39c7db598edccc2fcf7b27" + "sha256": "4ac9c7c1fc6873491907b81a74c29c6b992f90ca0bae82fa1397770c9919fc53" }, "src/openai/__init__.py": { "mode": "100644", @@ -39,7 +39,7 @@ }, "src/openai/resources/admin/organization/__init__.py": { "mode": "100644", - "sha256": "034a6e7dac771dd06b4d07424029f22465e9254e68c172a9b662e3b9b6cc5e61" + "sha256": "20413ef0ea8ce86960092122a76f79fe51a9cdb843175ae6939cf477cc8a808f" }, "src/openai/resources/admin/organization/admin_api_keys.py": { "mode": "100644", @@ -57,6 +57,10 @@ "mode": "100644", "sha256": "9173276962348404015e04efeb00c9551459bf7cec1ffe5890f164d1a76ee994" }, + "src/openai/resources/admin/organization/external_keys.py": { + "mode": "100644", + "sha256": "e1288655d595633c904b060833d7ebf0d9cfd0f285f10d0f8d08035d2f61e7be" + }, "src/openai/resources/admin/organization/external_storage.py": { "mode": "100644", "sha256": "3da24c9c52c8e39cdd6d8ca866193ea4879fbb4b865d5bc1b6a3f16b0fbd60ca" @@ -83,7 +87,7 @@ }, "src/openai/resources/admin/organization/organization.py": { "mode": "100644", - "sha256": "f038b43f0f1b020047682d864b49782ea1fb76c5d488def69b90407aa4be81a5" + "sha256": "254724f376de5fb777151c5531b4039d67f7e774fab9b71c2033d8de02a1f96c" }, "src/openai/resources/admin/organization/projects/__init__.py": { "mode": "100644", @@ -727,7 +731,7 @@ }, "src/openai/types/admin/organization/__init__.py": { "mode": "100644", - "sha256": "89fb55c8f13e4226f489ced9c8f19858f0dd5c97964f1402283b5c406972db30" + "sha256": "92e7a3a9265dfee7f51c4e87b5af288db6e07c3182a93eae31ed87161c75bf40" }, "src/openai/types/admin/organization/admin_api_key.py": { "mode": "100644", @@ -817,6 +821,22 @@ "mode": "100644", "sha256": "97a043bfbb91d7cb4b8f1a37709463637f1c96e95ed369edb39381324e95ede6" }, + "src/openai/types/admin/organization/external_key.py": { + "mode": "100644", + "sha256": "1dd59d578a766b90bd5b28852915627b1e4cd7e47374ac7d9fa5dcf16e0591fc" + }, + "src/openai/types/admin/organization/external_key_create_params.py": { + "mode": "100644", + "sha256": "b72cf37b3d75ab44fe26db1892dc04e372da3126500221d87a385449e425e4c5" + }, + "src/openai/types/admin/organization/external_key_list_params.py": { + "mode": "100644", + "sha256": "c7a4a6b062742f49632fedf361b45d87d862e7f9875b7523f3aeda062628f54a" + }, + "src/openai/types/admin/organization/external_key_validate_response.py": { + "mode": "100644", + "sha256": "1368ee3bad99b9aa0f8dc61e13b3dc2d00743e240c796b5be69204971d938657" + }, "src/openai/types/admin/organization/external_storage_configuration.py": { "mode": "100644", "sha256": "41a80b65975edd5db91e15a65df4ce24b1ce68f5ee0c6bf6f2023e7649c65553" @@ -7529,6 +7549,10 @@ "mode": "100644", "sha256": "f4fa22826e3f96847d60e1c431f3e7174e4ba59334dfa0d45a87f69a872bbf64" }, + "tests/api_resources/admin/organization/test_external_keys.py": { + "mode": "100644", + "sha256": "0bad3f5c69569508a864df6e84f6ee1b7660d78fb3da1c16eb13f6229ef71b7d" + }, "tests/api_resources/admin/organization/test_external_storage.py": { "mode": "100644", "sha256": "4d22bfb2e96bfcc5c5279997a5e1b93662d4ce194fdc08cb33f00fa270262b97" diff --git a/src/openai/resources/admin/organization/__init__.py b/src/openai/resources/admin/organization/__init__.py index d1036e4846..f3f43c513a 100644 --- a/src/openai/resources/admin/organization/__init__.py +++ b/src/openai/resources/admin/organization/__init__.py @@ -91,6 +91,14 @@ SpendAlertsWithStreamingResponse as SpendAlertsWithStreamingResponse, AsyncSpendAlertsWithStreamingResponse as AsyncSpendAlertsWithStreamingResponse, ) + from .external_keys import ( + ExternalKeys as ExternalKeys, + AsyncExternalKeys as AsyncExternalKeys, + ExternalKeysWithRawResponse as ExternalKeysWithRawResponse, + AsyncExternalKeysWithRawResponse as AsyncExternalKeysWithRawResponse, + ExternalKeysWithStreamingResponse as ExternalKeysWithStreamingResponse, + AsyncExternalKeysWithStreamingResponse as AsyncExternalKeysWithStreamingResponse, + ) from .admin_api_keys import ( AdminAPIKeys as AdminAPIKeys, AsyncAdminAPIKeys as AsyncAdminAPIKeys, @@ -196,6 +204,12 @@ "AsyncProjectsWithRawResponse": (".projects", "AsyncProjectsWithRawResponse"), "ProjectsWithStreamingResponse": (".projects", "ProjectsWithStreamingResponse"), "AsyncProjectsWithStreamingResponse": (".projects", "AsyncProjectsWithStreamingResponse"), + "ExternalKeys": (".external_keys", "ExternalKeys"), + "AsyncExternalKeys": (".external_keys", "AsyncExternalKeys"), + "ExternalKeysWithRawResponse": (".external_keys", "ExternalKeysWithRawResponse"), + "AsyncExternalKeysWithRawResponse": (".external_keys", "AsyncExternalKeysWithRawResponse"), + "ExternalKeysWithStreamingResponse": (".external_keys", "ExternalKeysWithStreamingResponse"), + "AsyncExternalKeysWithStreamingResponse": (".external_keys", "AsyncExternalKeysWithStreamingResponse"), "Organization": (".organization", "Organization"), "AsyncOrganization": (".organization", "AsyncOrganization"), "OrganizationWithRawResponse": (".organization", "OrganizationWithRawResponse"), @@ -208,6 +222,7 @@ "audit_logs", "certificates", "data_retention", + "external_keys", "external_storage", "groups", "invites", @@ -316,6 +331,12 @@ def __dir__() -> list[str]: "AsyncProjectsWithRawResponse", "ProjectsWithStreamingResponse", "AsyncProjectsWithStreamingResponse", + "ExternalKeys", + "AsyncExternalKeys", + "ExternalKeysWithRawResponse", + "AsyncExternalKeysWithRawResponse", + "ExternalKeysWithStreamingResponse", + "AsyncExternalKeysWithStreamingResponse", "Organization", "AsyncOrganization", "OrganizationWithRawResponse", diff --git a/src/openai/resources/admin/organization/external_keys.py b/src/openai/resources/admin/organization/external_keys.py new file mode 100644 index 0000000000..c8684a5776 --- /dev/null +++ b/src/openai/resources/admin/organization/external_keys.py @@ -0,0 +1,922 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import Literal, overload + +import httpx2 + +from .... import _legacy_response +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +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 to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ....pagination import SyncCursorPage, AsyncCursorPage +from ...._base_client import AsyncPaginator, make_request_options +from ....types.admin.organization import external_key_list_params, external_key_create_params +from ....types.admin.organization.external_key import ExternalKey +from ....types.admin.organization.external_key_validate_response import ExternalKeyValidateResponse + +__all__ = ["ExternalKeys", "AsyncExternalKeys"] + + +class ExternalKeys(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExternalKeysWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return ExternalKeysWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExternalKeysWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return ExternalKeysWithStreamingResponse(self) + + @overload + def create( + self, + *, + external_id: str, + kms_arn: str, + name: str, + role_arn: str, + type: Literal["aws"], + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Add an external key config for an organization + + Args: + external_id: The ExternalID that OpenAI will pass in the AssumeRole call for the customer's + AWS account. This must be the OpenAI organization ID or an API project ID that + belongs to the organization. + + kms_arn: The Key Management System ARN for the master key you manage + + name: A friendly name to identify this config + + role_arn: The Role ARN that OpenAI will assume to call your KMS + + 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 + """ + ... + + @overload + def create( + self, + *, + audience: str, + kms_key_location: str, + kms_key_name: str, + kms_key_ring_name: str, + kms_project_id: str, + name: str, + type: Literal["gcp"], + workload_identity_pool_id: str, + workload_identity_project_number: str, + workload_identity_provider_id: str, + kms_key_version: str | Omit = omit, + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Add an external key config for an organization + + Args: + audience: The aud field in the identity token OpenAI will pass in the call to the + customer's GCP STS. This must be the OpenAI organization ID or an API project ID + that belongs to the organization. + + kms_key_location: The region where your Key Management System master key is located + + kms_key_name: The name of the Key Management System master key you manage + + kms_key_ring_name: The Key Management System key ring for the master key you manage + + kms_project_id: The string GCP project name where your KMS resides + + name: A friendly name to identify this config + + workload_identity_pool_id: The pool containing the Workload Identity provider that you registered for + OpenAI + + workload_identity_project_number: The numeric GCP project number where you registered OpenAI's workload identity + + workload_identity_provider_id: The Workload Identity provider that you registered for OpenAI + + kms_key_version: An exact CryptoKeyVersion number. Required for PSP decryption; not accepted for + EKM. + + 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 + """ + ... + + @overload + def create( + self, + *, + key_name: str, + name: str, + tenant_id: str, + type: Literal["azure"], + vault_uri: str, + key_version: str | Omit = omit, + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Add an external key config for an organization + + Args: + key_name: The name of the Azure Key Vault master key that you manage + + name: A friendly name to identify this config + + tenant_id: Your Azure tenant UUID + + vault_uri: The URI of the Azure vault containing the master key you manage + + key_version: An exact Azure Key Vault key version. Required for PSP decryption; not accepted + for EKM. + + 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 + """ + ... + + @required_args( + ["external_id", "kms_arn", "name", "role_arn", "type"], + [ + "audience", + "kms_key_location", + "kms_key_name", + "kms_key_ring_name", + "kms_project_id", + "name", + "type", + "workload_identity_pool_id", + "workload_identity_project_number", + "workload_identity_provider_id", + ], + ["key_name", "name", "tenant_id", "type", "vault_uri"], + ) + def create( + self, + *, + external_id: str | Omit = omit, + kms_arn: str | Omit = omit, + name: str, + role_arn: str | Omit = omit, + type: Literal["aws"] | Literal["gcp"] | Literal["azure"], + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | Omit = omit, + audience: str | Omit = omit, + kms_key_location: str | Omit = omit, + kms_key_name: str | Omit = omit, + kms_key_ring_name: str | Omit = omit, + kms_project_id: str | Omit = omit, + workload_identity_pool_id: str | Omit = omit, + workload_identity_project_number: str | Omit = omit, + workload_identity_provider_id: str | Omit = omit, + kms_key_version: str | Omit = omit, + key_name: str | Omit = omit, + tenant_id: str | Omit = omit, + vault_uri: str | Omit = omit, + key_version: 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + return self._post( + "/organization/external_keys", + body=maybe_transform( + { + "external_id": external_id, + "kms_arn": kms_arn, + "name": name, + "role_arn": role_arn, + "type": type, + "purpose": purpose, + "audience": audience, + "kms_key_location": kms_key_location, + "kms_key_name": kms_key_name, + "kms_key_ring_name": kms_key_ring_name, + "kms_project_id": kms_project_id, + "workload_identity_pool_id": workload_identity_pool_id, + "workload_identity_project_number": workload_identity_project_number, + "workload_identity_provider_id": workload_identity_provider_id, + "kms_key_version": kms_key_version, + "key_name": key_name, + "tenant_id": tenant_id, + "vault_uri": vault_uri, + "key_version": key_version, + }, + external_key_create_params.ExternalKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKey, + ) + + def retrieve( + self, + external_key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Retrieve a stored external key registration for the user's organization. + + Args: + 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 external_key_id: + raise ValueError(f"Expected a non-empty value for `external_key_id` but received {external_key_id!r}") + return self._get( + path_template("/organization/external_keys/{external_key_id}", external_key_id=external_key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKey, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + purpose: Literal["ekm", "psp_decryption"] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> SyncCursorPage[ExternalKey]: + """ + List the external key configs registered on the user's organization + + Args: + purpose: Filter by key purpose. When omitted, returns keys of all purposes. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/external_keys", + page=SyncCursorPage[ExternalKey], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "purpose": purpose, + }, + external_key_list_params.ExternalKeyListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ExternalKey, + ) + + def delete( + self, + external_key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Delete an external key config for the user's organization + + Args: + 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 external_key_id: + raise ValueError(f"Expected a non-empty value for `external_key_id` but received {external_key_id!r}") + return self._delete( + path_template("/organization/external_keys/{external_key_id}", external_key_id=external_key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKey, + ) + + def validate( + self, + external_key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKeyValidateResponse: + """ + Validate an external key config for the user's organization + + Args: + 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 external_key_id: + raise ValueError(f"Expected a non-empty value for `external_key_id` but received {external_key_id!r}") + return self._post( + path_template("/organization/external_keys/{external_key_id}/validate", external_key_id=external_key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKeyValidateResponse, + ) + + +class AsyncExternalKeys(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExternalKeysWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/openai/openai-python#accessing-raw-response-data-eg-headers + """ + return AsyncExternalKeysWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExternalKeysWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/openai/openai-python#with_streaming_response + """ + return AsyncExternalKeysWithStreamingResponse(self) + + @overload + async def create( + self, + *, + external_id: str, + kms_arn: str, + name: str, + role_arn: str, + type: Literal["aws"], + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Add an external key config for an organization + + Args: + external_id: The ExternalID that OpenAI will pass in the AssumeRole call for the customer's + AWS account. This must be the OpenAI organization ID or an API project ID that + belongs to the organization. + + kms_arn: The Key Management System ARN for the master key you manage + + name: A friendly name to identify this config + + role_arn: The Role ARN that OpenAI will assume to call your KMS + + 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 + """ + ... + + @overload + async def create( + self, + *, + audience: str, + kms_key_location: str, + kms_key_name: str, + kms_key_ring_name: str, + kms_project_id: str, + name: str, + type: Literal["gcp"], + workload_identity_pool_id: str, + workload_identity_project_number: str, + workload_identity_provider_id: str, + kms_key_version: str | Omit = omit, + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Add an external key config for an organization + + Args: + audience: The aud field in the identity token OpenAI will pass in the call to the + customer's GCP STS. This must be the OpenAI organization ID or an API project ID + that belongs to the organization. + + kms_key_location: The region where your Key Management System master key is located + + kms_key_name: The name of the Key Management System master key you manage + + kms_key_ring_name: The Key Management System key ring for the master key you manage + + kms_project_id: The string GCP project name where your KMS resides + + name: A friendly name to identify this config + + workload_identity_pool_id: The pool containing the Workload Identity provider that you registered for + OpenAI + + workload_identity_project_number: The numeric GCP project number where you registered OpenAI's workload identity + + workload_identity_provider_id: The Workload Identity provider that you registered for OpenAI + + kms_key_version: An exact CryptoKeyVersion number. Required for PSP decryption; not accepted for + EKM. + + 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 + """ + ... + + @overload + async def create( + self, + *, + key_name: str, + name: str, + tenant_id: str, + type: Literal["azure"], + vault_uri: str, + key_version: str | Omit = omit, + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Add an external key config for an organization + + Args: + key_name: The name of the Azure Key Vault master key that you manage + + name: A friendly name to identify this config + + tenant_id: Your Azure tenant UUID + + vault_uri: The URI of the Azure vault containing the master key you manage + + key_version: An exact Azure Key Vault key version. Required for PSP decryption; not accepted + for EKM. + + 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 + """ + ... + + @required_args( + ["external_id", "kms_arn", "name", "role_arn", "type"], + [ + "audience", + "kms_key_location", + "kms_key_name", + "kms_key_ring_name", + "kms_project_id", + "name", + "type", + "workload_identity_pool_id", + "workload_identity_project_number", + "workload_identity_provider_id", + ], + ["key_name", "name", "tenant_id", "type", "vault_uri"], + ) + async def create( + self, + *, + external_id: str | Omit = omit, + kms_arn: str | Omit = omit, + name: str, + role_arn: str | Omit = omit, + type: Literal["aws"] | Literal["gcp"] | Literal["azure"], + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] | Omit = omit, + audience: str | Omit = omit, + kms_key_location: str | Omit = omit, + kms_key_name: str | Omit = omit, + kms_key_ring_name: str | Omit = omit, + kms_project_id: str | Omit = omit, + workload_identity_pool_id: str | Omit = omit, + workload_identity_project_number: str | Omit = omit, + workload_identity_provider_id: str | Omit = omit, + kms_key_version: str | Omit = omit, + key_name: str | Omit = omit, + tenant_id: str | Omit = omit, + vault_uri: str | Omit = omit, + key_version: 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + return await self._post( + "/organization/external_keys", + body=await async_maybe_transform( + { + "external_id": external_id, + "kms_arn": kms_arn, + "name": name, + "role_arn": role_arn, + "type": type, + "purpose": purpose, + "audience": audience, + "kms_key_location": kms_key_location, + "kms_key_name": kms_key_name, + "kms_key_ring_name": kms_key_ring_name, + "kms_project_id": kms_project_id, + "workload_identity_pool_id": workload_identity_pool_id, + "workload_identity_project_number": workload_identity_project_number, + "workload_identity_provider_id": workload_identity_provider_id, + "kms_key_version": kms_key_version, + "key_name": key_name, + "tenant_id": tenant_id, + "vault_uri": vault_uri, + "key_version": key_version, + }, + external_key_create_params.ExternalKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKey, + ) + + async def retrieve( + self, + external_key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Retrieve a stored external key registration for the user's organization. + + Args: + 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 external_key_id: + raise ValueError(f"Expected a non-empty value for `external_key_id` but received {external_key_id!r}") + return await self._get( + path_template("/organization/external_keys/{external_key_id}", external_key_id=external_key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKey, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + limit: int | Omit = omit, + order: Literal["asc", "desc"] | Omit = omit, + purpose: Literal["ekm", "psp_decryption"] | 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 | httpx2.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[ExternalKey, AsyncCursorPage[ExternalKey]]: + """ + List the external key configs registered on the user's organization + + Args: + purpose: Filter by key purpose. When omitted, returns keys of all purposes. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/organization/external_keys", + page=AsyncCursorPage[ExternalKey], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "limit": limit, + "order": order, + "purpose": purpose, + }, + external_key_list_params.ExternalKeyListParams, + ), + security={"admin_api_key_auth": True}, + ), + model=ExternalKey, + ) + + async def delete( + self, + external_key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKey: + """ + Delete an external key config for the user's organization + + Args: + 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 external_key_id: + raise ValueError(f"Expected a non-empty value for `external_key_id` but received {external_key_id!r}") + return await self._delete( + path_template("/organization/external_keys/{external_key_id}", external_key_id=external_key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKey, + ) + + async def validate( + self, + external_key_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx2.Timeout | None | NotGiven = not_given, + ) -> ExternalKeyValidateResponse: + """ + Validate an external key config for the user's organization + + Args: + 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 external_key_id: + raise ValueError(f"Expected a non-empty value for `external_key_id` but received {external_key_id!r}") + return await self._post( + path_template("/organization/external_keys/{external_key_id}/validate", external_key_id=external_key_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + security={"admin_api_key_auth": True}, + ), + cast_to=ExternalKeyValidateResponse, + ) + + +class ExternalKeysWithRawResponse: + def __init__(self, external_keys: ExternalKeys) -> None: + self._external_keys = external_keys + + self.create = _legacy_response.to_raw_response_wrapper( + external_keys.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + external_keys.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + external_keys.list, + ) + self.delete = _legacy_response.to_raw_response_wrapper( + external_keys.delete, + ) + self.validate = _legacy_response.to_raw_response_wrapper( + external_keys.validate, + ) + + +class AsyncExternalKeysWithRawResponse: + def __init__(self, external_keys: AsyncExternalKeys) -> None: + self._external_keys = external_keys + + self.create = _legacy_response.async_to_raw_response_wrapper( + external_keys.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + external_keys.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + external_keys.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + external_keys.delete, + ) + self.validate = _legacy_response.async_to_raw_response_wrapper( + external_keys.validate, + ) + + +class ExternalKeysWithStreamingResponse: + def __init__(self, external_keys: ExternalKeys) -> None: + self._external_keys = external_keys + + self.create = to_streamed_response_wrapper( + external_keys.create, + ) + self.retrieve = to_streamed_response_wrapper( + external_keys.retrieve, + ) + self.list = to_streamed_response_wrapper( + external_keys.list, + ) + self.delete = to_streamed_response_wrapper( + external_keys.delete, + ) + self.validate = to_streamed_response_wrapper( + external_keys.validate, + ) + + +class AsyncExternalKeysWithStreamingResponse: + def __init__(self, external_keys: AsyncExternalKeys) -> None: + self._external_keys = external_keys + + self.create = async_to_streamed_response_wrapper( + external_keys.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + external_keys.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + external_keys.list, + ) + self.delete = async_to_streamed_response_wrapper( + external_keys.delete, + ) + self.validate = async_to_streamed_response_wrapper( + external_keys.validate, + ) diff --git a/src/openai/resources/admin/organization/organization.py b/src/openai/resources/admin/organization/organization.py index f9d95942bd..162b4f0961 100644 --- a/src/openai/resources/admin/organization/organization.py +++ b/src/openai/resources/admin/organization/organization.py @@ -68,6 +68,14 @@ SpendAlertsWithStreamingResponse, AsyncSpendAlertsWithStreamingResponse, ) +from .external_keys import ( + ExternalKeys, + AsyncExternalKeys, + ExternalKeysWithRawResponse, + AsyncExternalKeysWithRawResponse, + ExternalKeysWithStreamingResponse, + AsyncExternalKeysWithStreamingResponse, +) from .groups.groups import ( Groups, AsyncGroups, @@ -166,6 +174,10 @@ def certificates(self) -> Certificates: def projects(self) -> Projects: return Projects(self._client) + @cached_property + def external_keys(self) -> ExternalKeys: + return ExternalKeys(self._client) + @cached_property def with_raw_response(self) -> OrganizationWithRawResponse: """ @@ -240,6 +252,10 @@ def certificates(self) -> AsyncCertificates: def projects(self) -> AsyncProjects: return AsyncProjects(self._client) + @cached_property + def external_keys(self) -> AsyncExternalKeys: + return AsyncExternalKeys(self._client) + @cached_property def with_raw_response(self) -> AsyncOrganizationWithRawResponse: """ @@ -317,6 +333,10 @@ def certificates(self) -> CertificatesWithRawResponse: def projects(self) -> ProjectsWithRawResponse: return ProjectsWithRawResponse(self._organization.projects) + @cached_property + def external_keys(self) -> ExternalKeysWithRawResponse: + return ExternalKeysWithRawResponse(self._organization.external_keys) + class AsyncOrganizationWithRawResponse: def __init__(self, organization: AsyncOrganization) -> None: @@ -375,6 +395,10 @@ def certificates(self) -> AsyncCertificatesWithRawResponse: def projects(self) -> AsyncProjectsWithRawResponse: return AsyncProjectsWithRawResponse(self._organization.projects) + @cached_property + def external_keys(self) -> AsyncExternalKeysWithRawResponse: + return AsyncExternalKeysWithRawResponse(self._organization.external_keys) + class OrganizationWithStreamingResponse: def __init__(self, organization: Organization) -> None: @@ -433,6 +457,10 @@ def certificates(self) -> CertificatesWithStreamingResponse: def projects(self) -> ProjectsWithStreamingResponse: return ProjectsWithStreamingResponse(self._organization.projects) + @cached_property + def external_keys(self) -> ExternalKeysWithStreamingResponse: + return ExternalKeysWithStreamingResponse(self._organization.external_keys) + class AsyncOrganizationWithStreamingResponse: def __init__(self, organization: AsyncOrganization) -> None: @@ -490,3 +518,7 @@ def certificates(self) -> AsyncCertificatesWithStreamingResponse: @cached_property def projects(self) -> AsyncProjectsWithStreamingResponse: return AsyncProjectsWithStreamingResponse(self._organization.projects) + + @cached_property + def external_keys(self) -> AsyncExternalKeysWithStreamingResponse: + return AsyncExternalKeysWithStreamingResponse(self._organization.external_keys) diff --git a/src/openai/types/admin/organization/__init__.py b/src/openai/types/admin/organization/__init__.py index 4ee75ea613..65d8538b65 100644 --- a/src/openai/types/admin/organization/__init__.py +++ b/src/openai/types/admin/organization/__init__.py @@ -7,6 +7,7 @@ from .invite import Invite as Invite from .project import Project as Project from .certificate import Certificate as Certificate +from .external_key import ExternalKey as ExternalKey from .admin_api_key import AdminAPIKey as AdminAPIKey from .role_list_params import RoleListParams as RoleListParams from .user_list_params import UserListParams as UserListParams @@ -39,6 +40,7 @@ from .certificate_list_params import CertificateListParams as CertificateListParams from .spend_alert_list_params import SpendAlertListParams as SpendAlertListParams from .usage_embeddings_params import UsageEmbeddingsParams as UsageEmbeddingsParams +from .external_key_list_params import ExternalKeyListParams as ExternalKeyListParams from .external_storage_deleted import ExternalStorageDeleted as ExternalStorageDeleted from .organization_spend_alert import OrganizationSpendAlert as OrganizationSpendAlert from .organization_spend_limit import OrganizationSpendLimit as OrganizationSpendLimit @@ -53,6 +55,7 @@ from .spend_alert_update_params import SpendAlertUpdateParams as SpendAlertUpdateParams from .spend_limit_update_params import SpendLimitUpdateParams as SpendLimitUpdateParams from .usage_embeddings_response import UsageEmbeddingsResponse as UsageEmbeddingsResponse +from .external_key_create_params import ExternalKeyCreateParams as ExternalKeyCreateParams from .usage_completions_response import UsageCompletionsResponse as UsageCompletionsResponse from .usage_moderations_response import UsageModerationsResponse as UsageModerationsResponse from .usage_vector_stores_params import UsageVectorStoresParams as UsageVectorStoresParams @@ -74,6 +77,7 @@ from .oci_external_storage_provider import OciExternalStorageProvider as OciExternalStorageProvider from .usage_audio_speeches_response import UsageAudioSpeechesResponse as UsageAudioSpeechesResponse from .usage_web_search_calls_params import UsageWebSearchCallsParams as UsageWebSearchCallsParams +from .external_key_validate_response import ExternalKeyValidateResponse as ExternalKeyValidateResponse from .external_storage_configuration import ExternalStorageConfiguration as ExternalStorageConfiguration from .external_storage_create_params import ExternalStorageCreateParams as ExternalStorageCreateParams from .usage_file_search_calls_params import UsageFileSearchCallsParams as UsageFileSearchCallsParams diff --git a/src/openai/types/admin/organization/external_key.py b/src/openai/types/admin/organization/external_key.py new file mode 100644 index 0000000000..105c25b106 --- /dev/null +++ b/src/openai/types/admin/organization/external_key.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ...._models import BaseModel + +__all__ = ["ExternalKey"] + + +class ExternalKey(BaseModel): + id: str + + api_project_ids: List[str] + + created_at: int + + name: str + + object: str + + type: str + + workspace_id: str + + audience: Optional[str] = None + + external_id: Optional[str] = None + + key_name: Optional[str] = None + + key_version: Optional[str] = None + + kms_arn: Optional[str] = None + + kms_key_location: Optional[str] = None + + kms_key_name: Optional[str] = None + + kms_key_ring_name: Optional[str] = None + + kms_key_version: Optional[str] = None + + kms_project_id: Optional[str] = None + + purpose: Optional[Literal["ekm", "psp_decryption"]] = None + + role_arn: Optional[str] = None + + tenant_id: Optional[str] = None + + vault_uri: Optional[str] = None + + workload_identity_pool_id: Optional[str] = None + + workload_identity_project_number: Optional[str] = None + + workload_identity_provider_id: Optional[str] = None diff --git a/src/openai/types/admin/organization/external_key_create_params.py b/src/openai/types/admin/organization/external_key_create_params.py new file mode 100644 index 0000000000..c8d0c971c1 --- /dev/null +++ b/src/openai/types/admin/organization/external_key_create_params.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = [ + "ExternalKeyCreateParams", + "V1OrganizationExternalKeysPostRequestAws", + "V1OrganizationExternalKeysPostRequestGcp", + "V1OrganizationExternalKeysPostRequestAzure", +] + + +class V1OrganizationExternalKeysPostRequestAws(TypedDict, total=False): + external_id: Required[str] + + kms_arn: Required[str] + + name: Required[str] + + role_arn: Required[str] + + type: Required[Literal["aws"]] + + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] + + +class V1OrganizationExternalKeysPostRequestGcp(TypedDict, total=False): + audience: Required[str] + + kms_key_location: Required[str] + + kms_key_name: Required[str] + + kms_key_ring_name: Required[str] + + kms_project_id: Required[str] + + name: Required[str] + + type: Required[Literal["gcp"]] + + workload_identity_pool_id: Required[str] + + workload_identity_project_number: Required[str] + + workload_identity_provider_id: Required[str] + + kms_key_version: str + + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] + + +class V1OrganizationExternalKeysPostRequestAzure(TypedDict, total=False): + key_name: Required[str] + + name: Required[str] + + tenant_id: Required[str] + + type: Required[Literal["azure"]] + + vault_uri: Required[str] + + key_version: str + + purpose: Union[Literal["ekm"], Literal["psp_decryption"]] + + +ExternalKeyCreateParams: TypeAlias = Union[ + V1OrganizationExternalKeysPostRequestAws, + V1OrganizationExternalKeysPostRequestGcp, + V1OrganizationExternalKeysPostRequestAzure, +] diff --git a/src/openai/types/admin/organization/external_key_list_params.py b/src/openai/types/admin/organization/external_key_list_params.py new file mode 100644 index 0000000000..76957acc69 --- /dev/null +++ b/src/openai/types/admin/organization/external_key_list_params.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["ExternalKeyListParams"] + + +class ExternalKeyListParams(TypedDict, total=False): + after: Optional[str] + + limit: int + + order: Literal["asc", "desc"] + + purpose: Literal["ekm", "psp_decryption"] + """Filter by key purpose. When omitted, returns keys of all purposes.""" diff --git a/src/openai/types/admin/organization/external_key_validate_response.py b/src/openai/types/admin/organization/external_key_validate_response.py new file mode 100644 index 0000000000..eefb9ef51b --- /dev/null +++ b/src/openai/types/admin/organization/external_key_validate_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from ...._models import BaseModel + +__all__ = ["ExternalKeyValidateResponse"] + + +class ExternalKeyValidateResponse(BaseModel): + status: str diff --git a/tests/api_resources/admin/organization/test_external_keys.py b/tests/api_resources/admin/organization/test_external_keys.py new file mode 100644 index 0000000000..54cf79f13e --- /dev/null +++ b/tests/api_resources/admin/organization/test_external_keys.py @@ -0,0 +1,700 @@ +# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.pagination import SyncCursorPage, AsyncCursorPage +from openai.types.admin.organization import ( + ExternalKey, + ExternalKeyValidateResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExternalKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create_overload_1(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_1(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + purpose="ekm", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_raw_response_create_overload_1(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_2(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + kms_key_version="kms_key_version", + purpose="ekm", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_raw_response_create_overload_2(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_create_overload_3(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params_overload_3(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + key_version="key_version", + purpose="ekm", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_raw_response_create_overload_3(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_streaming_response_create_overload_3(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.retrieve( + "external_key_id", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.retrieve( + "external_key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.retrieve( + "external_key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_key_id` but received ''"): + client.admin.organization.external_keys.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.list() + assert_matches_type(SyncCursorPage[ExternalKey], external_key, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.list( + after="after", + limit=0, + order="asc", + purpose="ekm", + ) + assert_matches_type(SyncCursorPage[ExternalKey], external_key, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(SyncCursorPage[ExternalKey], external_key, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(SyncCursorPage[ExternalKey], external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_delete(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.delete( + "external_key_id", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.delete( + "external_key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.delete( + "external_key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_key_id` but received ''"): + client.admin.organization.external_keys.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_validate(self, client: OpenAI) -> None: + external_key = client.admin.organization.external_keys.validate( + "external_key_id", + ) + assert_matches_type(ExternalKeyValidateResponse, external_key, path=["response"]) + + @parametrize + def test_raw_response_validate(self, client: OpenAI) -> None: + response = client.admin.organization.external_keys.with_raw_response.validate( + "external_key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKeyValidateResponse, external_key, path=["response"]) + + @parametrize + def test_streaming_response_validate(self, client: OpenAI) -> None: + with client.admin.organization.external_keys.with_streaming_response.validate( + "external_key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = response.parse() + assert_matches_type(ExternalKeyValidateResponse, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_validate(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_key_id` but received ''"): + client.admin.organization.external_keys.with_raw_response.validate( + "", + ) + + +class TestAsyncExternalKeys: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create_overload_1(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + purpose="ekm", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_1(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.create( + external_id="external_id", + kms_arn="kms_arn", + name="name", + role_arn='arn:aws:iam::732166991022:role/0*bWz"J5bo7', + type="aws", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + kms_key_version="kms_key_version", + purpose="ekm", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.create( + audience="audience", + kms_key_location="kms_key_location", + kms_key_name="kms_key_name", + kms_key_ring_name="kms_key_ring_name", + kms_project_id="kms_project_id", + name="name", + type="gcp", + workload_identity_pool_id="workload_identity_pool_id", + workload_identity_project_number="workload_identity_project_number", + workload_identity_provider_id="workload_identity_provider_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_create_overload_3(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params_overload_3(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + key_version="key_version", + purpose="ekm", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_raw_response_create_overload_3(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_streaming_response_create_overload_3(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.create( + key_name="key_name", + name="name", + tenant_id="tenant_id", + type="azure", + vault_uri="vault_uri", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.retrieve( + "external_key_id", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.retrieve( + "external_key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.retrieve( + "external_key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_key_id` but received ''"): + await async_client.admin.organization.external_keys.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.list() + assert_matches_type(AsyncCursorPage[ExternalKey], external_key, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.list( + after="after", + limit=0, + order="asc", + purpose="ekm", + ) + assert_matches_type(AsyncCursorPage[ExternalKey], external_key, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(AsyncCursorPage[ExternalKey], external_key, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(AsyncCursorPage[ExternalKey], external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.delete( + "external_key_id", + ) + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.delete( + "external_key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.delete( + "external_key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(ExternalKey, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_key_id` but received ''"): + await async_client.admin.organization.external_keys.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_validate(self, async_client: AsyncOpenAI) -> None: + external_key = await async_client.admin.organization.external_keys.validate( + "external_key_id", + ) + assert_matches_type(ExternalKeyValidateResponse, external_key, path=["response"]) + + @parametrize + async def test_raw_response_validate(self, async_client: AsyncOpenAI) -> None: + response = await async_client.admin.organization.external_keys.with_raw_response.validate( + "external_key_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_key = response.parse() + assert_matches_type(ExternalKeyValidateResponse, external_key, path=["response"]) + + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncOpenAI) -> None: + async with async_client.admin.organization.external_keys.with_streaming_response.validate( + "external_key_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_key = await response.parse() + assert_matches_type(ExternalKeyValidateResponse, external_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_validate(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `external_key_id` but received ''"): + await async_client.admin.organization.external_keys.with_raw_response.validate( + "", + ) diff --git a/tests/test_external_keys_contract.py b/tests/test_external_keys_contract.py new file mode 100644 index 0000000000..f4e4a45818 --- /dev/null +++ b/tests/test_external_keys_contract.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import json +from typing import Any, Sequence, cast + +import httpx2 +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.respx2 import MockRouter +from tests.respx2.models import Call + +BASE_URL = "https://example.com/v1" +PROVIDERS: list[dict[str, Any]] = [ + { + "type": "aws", + "name": "AWS", + "role_arn": "arn:aws:iam::000000000000:role/test", + "external_id": "org_test", + "kms_arn": "arn:aws:kms:us-east-1:000000000000:key/test", + }, + { + "type": "gcp", + "name": "GCP", + "audience": "org_test", + "kms_key_location": "global", + "kms_key_name": "key", + "kms_key_ring_name": "ring", + "kms_project_id": "project", + "workload_identity_pool_id": "pool", + "workload_identity_project_number": "123", + "workload_identity_provider_id": "provider", + "purpose": "psp_decryption", + "kms_key_version": "1", + }, + { + "type": "azure", + "name": "Azure", + "key_name": "org_test--key", + "tenant_id": "tenant", + "vault_uri": "https://example.vault.azure.net", + "purpose": "psp_decryption", + "key_version": "version", + }, +] + + +def key_payload(key_id: str = "extkey_test", object_type: str = "organization.external_key") -> dict[str, Any]: + return { + "id": key_id, + "object": object_type, + "type": "aws", + "name": "test", + "api_project_ids": [], + "workspace_id": "", + "created_at": 123, + } + + +@pytest.mark.respx2(base_url=BASE_URL) +@pytest.mark.parametrize("provider", PROVIDERS, ids=["aws", "gcp", "azure"]) +def test_external_key_providers(provider: dict[str, Any], respx2_mock: MockRouter) -> None: + payload = key_payload() + route = respx2_mock.post("/organization/external_keys").mock(return_value=httpx2.Response(200, json=payload)) + with OpenAI( + base_url=BASE_URL, api_key="project-test", admin_api_key="admin-test", _strict_response_validation=True + ) as client: + result = client.admin.organization.external_keys.create(**provider) + assert result.to_dict() == payload + assert result.purpose is None + assert route.calls.last.request.headers["Authorization"] == "Bearer admin-test" + assert json.loads(route.calls.last.request.content) == provider + + +@pytest.mark.respx2(base_url=BASE_URL) +@pytest.mark.parametrize("provider", PROVIDERS, ids=["aws", "gcp", "azure"]) +async def test_async_external_key_providers(provider: dict[str, Any], respx2_mock: MockRouter) -> None: + payload = key_payload() + route = respx2_mock.post("/organization/external_keys").mock(return_value=httpx2.Response(200, json=payload)) + async with AsyncOpenAI( + base_url=BASE_URL, api_key="project-test", admin_api_key="admin-test", _strict_response_validation=True + ) as client: + result = await client.admin.organization.external_keys.create(**provider) + assert result.to_dict() == payload + assert route.calls.last.request.headers["Authorization"] == "Bearer admin-test" + assert json.loads(route.calls.last.request.content) == provider + + +def mock_pages(respx2_mock: MockRouter) -> None: + respx2_mock.get("/organization/external_keys").mock( + side_effect=[ + httpx2.Response( + 200, + json={ + "object": "list", + "data": [key_payload(key_id)], + "first_id": key_id, + "last_id": key_id, + "has_more": more, + }, + ) + for key_id, more in [("extkey_first", True), ("extkey_last", False)] + ] + ) + + +def recorded_requests(respx2_mock: MockRouter) -> list[httpx2.Request]: + return [call.request for call in cast(Sequence[Call], respx2_mock.calls)] + + +def assert_pages(respx2_mock: MockRouter) -> None: + requests = recorded_requests(respx2_mock) + assert [dict(request.url.params) for request in requests] == [ + {"purpose": "psp_decryption", "limit": "1"}, + {"purpose": "psp_decryption", "limit": "1", "after": "extkey_first"}, + ] + assert all(request.headers["Authorization"] == "Bearer admin-test" for request in requests) + + +@pytest.mark.respx2(base_url=BASE_URL) +def test_external_key_pagination(respx2_mock: MockRouter) -> None: + mock_pages(respx2_mock) + with OpenAI(base_url=BASE_URL, api_key="project-test", admin_api_key="admin-test") as client: + items = list(client.admin.organization.external_keys.list(purpose="psp_decryption", limit=1)) + assert [item.id for item in items] == ["extkey_first", "extkey_last"] + assert_pages(respx2_mock) + + +@pytest.mark.respx2(base_url=BASE_URL) +async def test_async_external_key_pagination(respx2_mock: MockRouter) -> None: + mock_pages(respx2_mock) + async with AsyncOpenAI(base_url=BASE_URL, api_key="project-test", admin_api_key="admin-test") as client: + items = [item async for item in client.admin.organization.external_keys.list(purpose="psp_decryption", limit=1)] + assert [item.id for item in items] == ["extkey_first", "extkey_last"] + assert_pages(respx2_mock) + + +@pytest.mark.respx2(base_url=BASE_URL) +def test_external_key_lifecycle_and_raw_response(respx2_mock: MockRouter) -> None: + base = "/organization/external_keys/extkey_test" + respx2_mock.get(base).mock(return_value=httpx2.Response(200, json=key_payload())) + respx2_mock.post(base + "/validate").mock(return_value=httpx2.Response(200, json={"status": "valid"})) + deleted = key_payload(object_type="organization.external_key.deleted") + respx2_mock.delete(base).mock(return_value=httpx2.Response(200, json=deleted)) + with OpenAI(base_url=BASE_URL, api_key="project-test", admin_api_key="admin-test") as client: + keys = client.admin.organization.external_keys + retrieved = keys.with_raw_response.retrieve("extkey_test").parse() + validated = keys.validate("extkey_test") + deletion = keys.delete("extkey_test") + assert retrieved.id == "extkey_test" + assert validated.status == "valid" + assert deletion.to_dict() == deleted + assert all(request.headers["Authorization"] == "Bearer admin-test" for request in recorded_requests(respx2_mock)) + + +@pytest.mark.respx2(base_url=BASE_URL) +async def test_async_external_key_lifecycle_and_streamed_response(respx2_mock: MockRouter) -> None: + base = "/organization/external_keys/extkey_test" + respx2_mock.get(base).mock(return_value=httpx2.Response(200, json=key_payload())) + respx2_mock.post(base + "/validate").mock(return_value=httpx2.Response(200, json={"status": "valid"})) + deleted = key_payload(object_type="organization.external_key.deleted") + respx2_mock.delete(base).mock(return_value=httpx2.Response(200, json=deleted)) + async with AsyncOpenAI(base_url=BASE_URL, api_key="project-test", admin_api_key="admin-test") as client: + keys = client.admin.organization.external_keys + async with keys.with_streaming_response.retrieve("extkey_test") as response: + retrieved = await response.parse() + validated = await keys.validate("extkey_test") + deletion = await keys.delete("extkey_test") + assert retrieved.id == "extkey_test" + assert validated.status == "valid" + assert deletion.to_dict() == deleted + assert all(request.headers["Authorization"] == "Bearer admin-test" for request in recorded_requests(respx2_mock))