diff --git a/.castiron.stats.yml b/.castiron.stats.yml
index e627ad7cf1..ba5189c113 100644
--- a/.castiron.stats.yml
+++ b/.castiron.stats.yml
@@ -1,8 +1,8 @@
schema_version: 1
-generation_id: 0d99919e-4341-4042-88cf-64ec38034f9d
-openapi_spec_hash: 72dd9904e4616e46924320e49100670e
-openapi_transformed_spec_hash: e7d24da6852468e1b83db4468b3151de
-config_hash: c426d2799884f277047e545f57fad0c8
-codegen_sha: 273299f9b1b1d4aec76dd3f3c8053f24c99e5a6a
-codegen_hash: 22535fd7ebf99f054ee5627760908ae8c99c24130de1e9b3a150ba51a75a29c6
-public_codegen_sha: 8441291d5011f90d19b1ec9d3ed5ff9cbf7add16
+generation_id: 4381598a-5a3e-4b2c-b37b-65ab559dc25e
+openapi_spec_hash: f7fd2b85cc9e2a36081f6cb53f30c8a2
+openapi_transformed_spec_hash: 14712113bc9b12516d9a0a43d4e355f2
+config_hash: 60435bd668a5724c530fe7e9d6869e9a
+codegen_sha: 702ca0d6e39bf6d90ac1c8d331f8457644d5177e
+codegen_hash: 210d8e7aa966e14b48b838013ad2991c06262acab2a7f60c98f462218217fdb0
+public_codegen_sha: 680f9c0f4cc7960f95cf1293e761218ce883597e
diff --git a/api.md b/api.md
index ad814fa255..fac8e7ea84 100644
--- a/api.md
+++ b/api.md
@@ -463,6 +463,18 @@ Methods:
- client.safety.alerts.retrieve(id) -> SafetyAlert
+## Cases
+
+Types:
+
+```python
+from openai.types.safety import SafetyCase
+```
+
+Methods:
+
+- client.safety.cases.retrieve(id) -> SafetyCase
+
# [Webhooks](src/openai/resources/webhooks/api.md)
Methods:
diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml
index aaf2378714..0a78d772e8 100644
--- a/api_reference/openapi.transformed.yml
+++ b/api_reference/openapi.transformed.yml
@@ -22620,6 +22620,44 @@ paths:
"created_at": 1741900000,
"metadata": {"topic": "project-x"}
}
+ /safety/cases/{id}:
+ get:
+ summary: Get safety case
+ description: Get a safety case by ID.
+ operationId: Getsafetycase
+ parameters:
+ - name: id
+ in: path
+ description: Safety case ID
+ required: true
+ schema:
+ description: Safety case ID
+ type: string
+ maxLength: 128
+ responses:
+ '200':
+ description: Success
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SafetyCaseResource'
+ x-oaiMeta:
+ group: safety-cases
+ path: retrieve
+ examples:
+ request:
+ curl: |
+ curl https://api.openai.com/v1/safety/cases/C-abc123 \
+ -H "Authorization: Bearer $OPENAI_API_KEY"
+ response: |
+ {
+ "id": "C-abc123",
+ "object": "safety.case",
+ "created_at": 1787659200,
+ "entity_identifier": "safety-id-123",
+ "reason": "cyber_abuse",
+ "notice": {"type": "warning"}
+ }
/safety/alerts/{id}:
get:
summary: Get project safety alert
@@ -71405,6 +71443,47 @@ components:
- object
- deleted
- id
+ SafetyCaseNoticeType:
+ type: string
+ enum:
+ - warning
+ - deactivation
+ SafetyCaseNotice:
+ properties:
+ type:
+ $ref: '#/components/schemas/SafetyCaseNoticeType'
+ type: object
+ required:
+ - type
+ SafetyCaseResource:
+ properties:
+ id:
+ type: string
+ object:
+ type: string
+ enum:
+ - safety.case
+ default: safety.case
+ x-stainless-const: true
+ created_at:
+ type: integer
+ format: unixtime
+ entity_identifier:
+ type: string
+ reason:
+ anyOf:
+ - type: string
+ - type: 'null'
+ notice:
+ $ref: '#/components/schemas/SafetyCaseNotice'
+ type: object
+ required:
+ - id
+ - object
+ - created_at
+ - entity_identifier
+ - reason
+ - notice
SafetyAlertErrorType:
type: string
enum:
@@ -97121,6 +97200,20 @@ x-oaiMeta:
- type: object
key: ResponseErrorEvent
path:
+ - id: safety-cases
+ title: Safety Cases
+ description: |
+ Retrieve details about a safety warning or deactivation using the case ID
+ from a `safety.warning_issued` or `safety.deactivation_issued` webhook event.
+ Cases belong to an organization and require an API key with `api.safety.read`.
+ navigationGroup: endpoints
+ sections:
+ - type: endpoint
+ key: Getsafetycase
+ path: retrieve
+ - type: object
+ key: SafetyCaseResource
+ path: object
- id: safety-alerts
title: Safety Alerts
description: |
diff --git a/src/openai/resources/safety/__init__.py b/src/openai/resources/safety/__init__.py
index 696de547f5..57d50e6bc3 100644
--- a/src/openai/resources/safety/__init__.py
+++ b/src/openai/resources/safety/__init__.py
@@ -3,6 +3,14 @@
import typing as _t
if _t.TYPE_CHECKING:
+ from .cases import (
+ Cases as Cases,
+ AsyncCases as AsyncCases,
+ CasesWithRawResponse as CasesWithRawResponse,
+ AsyncCasesWithRawResponse as AsyncCasesWithRawResponse,
+ CasesWithStreamingResponse as CasesWithStreamingResponse,
+ AsyncCasesWithStreamingResponse as AsyncCasesWithStreamingResponse,
+ )
from .alerts import (
Alerts as Alerts,
AsyncAlerts as AsyncAlerts,
@@ -28,6 +36,12 @@
"AsyncAlertsWithRawResponse": (".alerts", "AsyncAlertsWithRawResponse"),
"AlertsWithStreamingResponse": (".alerts", "AlertsWithStreamingResponse"),
"AsyncAlertsWithStreamingResponse": (".alerts", "AsyncAlertsWithStreamingResponse"),
+ "Cases": (".cases", "Cases"),
+ "AsyncCases": (".cases", "AsyncCases"),
+ "CasesWithRawResponse": (".cases", "CasesWithRawResponse"),
+ "AsyncCasesWithRawResponse": (".cases", "AsyncCasesWithRawResponse"),
+ "CasesWithStreamingResponse": (".cases", "CasesWithStreamingResponse"),
+ "AsyncCasesWithStreamingResponse": (".cases", "AsyncCasesWithStreamingResponse"),
"Safety": (".safety", "Safety"),
"AsyncSafety": (".safety", "AsyncSafety"),
"SafetyWithRawResponse": (".safety", "SafetyWithRawResponse"),
@@ -37,6 +51,7 @@
}
_SUBMODULES = {
"alerts",
+ "cases",
"safety",
}
@@ -64,6 +79,12 @@ def __dir__() -> list[str]:
"AsyncAlertsWithRawResponse",
"AlertsWithStreamingResponse",
"AsyncAlertsWithStreamingResponse",
+ "Cases",
+ "AsyncCases",
+ "CasesWithRawResponse",
+ "AsyncCasesWithRawResponse",
+ "CasesWithStreamingResponse",
+ "AsyncCasesWithStreamingResponse",
"Safety",
"AsyncSafety",
"SafetyWithRawResponse",
diff --git a/src/openai/resources/safety/cases.py b/src/openai/resources/safety/cases.py
new file mode 100644
index 0000000000..7c59f4d71d
--- /dev/null
+++ b/src/openai/resources/safety/cases.py
@@ -0,0 +1,172 @@
+# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx2
+
+from ... import _legacy_response
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
+from ..._base_client import make_request_options
+from ...types.safety.safety_case import SafetyCase
+
+__all__ = ["Cases", "AsyncCases"]
+
+
+class Cases(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> CasesWithRawResponse:
+ """
+ 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 CasesWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CasesWithStreamingResponse:
+ """
+ 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 CasesWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ 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,
+ ) -> SafetyCase:
+ """
+ Get a safety case by ID.
+
+ Args:
+ id: Safety case 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 id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/safety/cases/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=SafetyCase,
+ )
+
+
+class AsyncCases(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncCasesWithRawResponse:
+ """
+ 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 AsyncCasesWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCasesWithStreamingResponse:
+ """
+ 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 AsyncCasesWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ 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,
+ ) -> SafetyCase:
+ """
+ Get a safety case by ID.
+
+ Args:
+ id: Safety case 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 id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/safety/cases/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={"bearer_auth": True},
+ ),
+ cast_to=SafetyCase,
+ )
+
+
+class CasesWithRawResponse:
+ def __init__(self, cases: Cases) -> None:
+ self._cases = cases
+
+ self.retrieve = _legacy_response.to_raw_response_wrapper(
+ cases.retrieve,
+ )
+
+
+class AsyncCasesWithRawResponse:
+ def __init__(self, cases: AsyncCases) -> None:
+ self._cases = cases
+
+ self.retrieve = _legacy_response.async_to_raw_response_wrapper(
+ cases.retrieve,
+ )
+
+
+class CasesWithStreamingResponse:
+ def __init__(self, cases: Cases) -> None:
+ self._cases = cases
+
+ self.retrieve = to_streamed_response_wrapper(
+ cases.retrieve,
+ )
+
+
+class AsyncCasesWithStreamingResponse:
+ def __init__(self, cases: AsyncCases) -> None:
+ self._cases = cases
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ cases.retrieve,
+ )
diff --git a/src/openai/resources/safety/safety.py b/src/openai/resources/safety/safety.py
index 80aeb916d5..4d80f73eda 100644
--- a/src/openai/resources/safety/safety.py
+++ b/src/openai/resources/safety/safety.py
@@ -2,6 +2,14 @@
from __future__ import annotations
+from .cases import (
+ Cases,
+ AsyncCases,
+ CasesWithRawResponse,
+ AsyncCasesWithRawResponse,
+ CasesWithStreamingResponse,
+ AsyncCasesWithStreamingResponse,
+)
from .alerts import (
Alerts,
AsyncAlerts,
@@ -21,6 +29,10 @@ class Safety(SyncAPIResource):
def alerts(self) -> Alerts:
return Alerts(self._client)
+ @cached_property
+ def cases(self) -> Cases:
+ return Cases(self._client)
+
@cached_property
def with_raw_response(self) -> SafetyWithRawResponse:
"""
@@ -46,6 +58,10 @@ class AsyncSafety(AsyncAPIResource):
def alerts(self) -> AsyncAlerts:
return AsyncAlerts(self._client)
+ @cached_property
+ def cases(self) -> AsyncCases:
+ return AsyncCases(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncSafetyWithRawResponse:
"""
@@ -74,6 +90,10 @@ def __init__(self, safety: Safety) -> None:
def alerts(self) -> AlertsWithRawResponse:
return AlertsWithRawResponse(self._safety.alerts)
+ @cached_property
+ def cases(self) -> CasesWithRawResponse:
+ return CasesWithRawResponse(self._safety.cases)
+
class AsyncSafetyWithRawResponse:
def __init__(self, safety: AsyncSafety) -> None:
@@ -83,6 +103,10 @@ def __init__(self, safety: AsyncSafety) -> None:
def alerts(self) -> AsyncAlertsWithRawResponse:
return AsyncAlertsWithRawResponse(self._safety.alerts)
+ @cached_property
+ def cases(self) -> AsyncCasesWithRawResponse:
+ return AsyncCasesWithRawResponse(self._safety.cases)
+
class SafetyWithStreamingResponse:
def __init__(self, safety: Safety) -> None:
@@ -92,6 +116,10 @@ def __init__(self, safety: Safety) -> None:
def alerts(self) -> AlertsWithStreamingResponse:
return AlertsWithStreamingResponse(self._safety.alerts)
+ @cached_property
+ def cases(self) -> CasesWithStreamingResponse:
+ return CasesWithStreamingResponse(self._safety.cases)
+
class AsyncSafetyWithStreamingResponse:
def __init__(self, safety: AsyncSafety) -> None:
@@ -100,3 +128,7 @@ def __init__(self, safety: AsyncSafety) -> None:
@cached_property
def alerts(self) -> AsyncAlertsWithStreamingResponse:
return AsyncAlertsWithStreamingResponse(self._safety.alerts)
+
+ @cached_property
+ def cases(self) -> AsyncCasesWithStreamingResponse:
+ return AsyncCasesWithStreamingResponse(self._safety.cases)
diff --git a/src/openai/types/safety/__init__.py b/src/openai/types/safety/__init__.py
index 00f62d53d2..406a8fa411 100644
--- a/src/openai/types/safety/__init__.py
+++ b/src/openai/types/safety/__init__.py
@@ -2,4 +2,5 @@
from __future__ import annotations
+from .safety_case import SafetyCase as SafetyCase
from .safety_alert import SafetyAlert as SafetyAlert
diff --git a/src/openai/types/safety/safety_case.py b/src/openai/types/safety/safety_case.py
new file mode 100644
index 0000000000..7a5d59aa76
--- /dev/null
+++ b/src/openai/types/safety/safety_case.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+
+__all__ = ["SafetyCase", "Notice"]
+
+
+class Notice(BaseModel):
+ type: Literal["warning", "deactivation"]
+
+
+class SafetyCase(BaseModel):
+ id: str
+
+ created_at: int
+
+ entity_identifier: str
+
+ notice: Notice
+
+ object: Literal["safety.case"]
+
+ reason: Optional[str] = None
diff --git a/tests/api_resources/safety/test_cases.py b/tests/api_resources/safety/test_cases.py
new file mode 100644
index 0000000000..486576049b
--- /dev/null
+++ b/tests/api_resources/safety/test_cases.py
@@ -0,0 +1,100 @@
+# 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.types.safety import SafetyCase
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCases:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_retrieve(self, client: OpenAI) -> None:
+ case = client.safety.cases.retrieve(
+ "id",
+ )
+ assert_matches_type(SafetyCase, case, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: OpenAI) -> None:
+ response = client.safety.cases.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ case = response.parse()
+ assert_matches_type(SafetyCase, case, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: OpenAI) -> None:
+ with client.safety.cases.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ case = response.parse()
+ assert_matches_type(SafetyCase, case, 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 `id` but received ''"):
+ client.safety.cases.with_raw_response.retrieve(
+ "",
+ )
+
+
+class TestAsyncCases:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None:
+ case = await async_client.safety.cases.retrieve(
+ "id",
+ )
+ assert_matches_type(SafetyCase, case, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None:
+ response = await async_client.safety.cases.with_raw_response.retrieve(
+ "id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ case = response.parse()
+ assert_matches_type(SafetyCase, case, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None:
+ async with async_client.safety.cases.with_streaming_response.retrieve(
+ "id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ case = await response.parse()
+ assert_matches_type(SafetyCase, case, 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 `id` but received ''"):
+ await async_client.safety.cases.with_raw_response.retrieve(
+ "",
+ )
diff --git a/tests/test_safety_cases.py b/tests/test_safety_cases.py
new file mode 100644
index 0000000000..e01cb6aeb4
--- /dev/null
+++ b/tests/test_safety_cases.py
@@ -0,0 +1,141 @@
+from __future__ import annotations
+
+from collections.abc import Callable
+
+import httpx2
+import pytest
+
+from openai import OpenAI, AsyncOpenAI
+from openai.types.safety import SafetyCase
+
+BASE_URL = "https://example.com/v1"
+CASE_ID = "case/with ?#%"
+
+
+def safety_case(notice_type: str, reason: str | None) -> dict[str, object]:
+ return {
+ "id": CASE_ID,
+ "object": "safety.case",
+ "created_at": 123,
+ "entity_identifier": "synthetic-entity",
+ "reason": reason,
+ "notice": {"type": notice_type, "future_notice_field": "retained"},
+ "future_case_field": {"retained": True},
+ }
+
+
+def response_handler(
+ payload: dict[str, object], requests: list[httpx2.Request]
+) -> Callable[[httpx2.Request], httpx2.Response]:
+ def handle(request: httpx2.Request) -> httpx2.Response:
+ requests.append(request)
+ return httpx2.Response(200, json=payload)
+
+ return handle
+
+
+def assert_request(requests: list[httpx2.Request]) -> None:
+ assert len(requests) == 1
+ request = requests[0]
+ assert request.method == "GET"
+ assert request.url.raw_path.split(b"?", 1)[0] == b"/v1/safety/cases/case%2Fwith%20%3F%23%25"
+ assert dict(request.url.params) == {"trace": "contract"}
+ assert request.content == b""
+ assert request.headers["Authorization"] == "Bearer test-project-key"
+ assert request.headers["X-Case-Trace"] == "caller-owned"
+
+
+@pytest.mark.parametrize("notice_type", ["warning", "deactivation"])
+@pytest.mark.parametrize("reason", [None, "synthetic reason"])
+def test_sync_safety_case_contract(notice_type: str, reason: str | None) -> None:
+ payload = safety_case(notice_type, reason)
+ requests: list[httpx2.Request] = []
+ with OpenAI(
+ base_url=BASE_URL,
+ api_key="test-project-key",
+ admin_api_key="test-admin-key",
+ http_client=httpx2.Client(transport=httpx2.MockTransport(response_handler(payload, requests))),
+ _strict_response_validation=True,
+ ) as client:
+ result = client.safety.cases.retrieve(
+ CASE_ID,
+ extra_headers={"X-Case-Trace": "caller-owned"},
+ extra_query={"trace": "contract"},
+ )
+
+ assert isinstance(result, SafetyCase)
+ assert result.to_dict() == payload
+ assert_request(requests)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("notice_type", ["warning", "deactivation"])
+@pytest.mark.parametrize("reason", [None, "synthetic reason"])
+async def test_async_safety_case_contract(notice_type: str, reason: str | None) -> None:
+ payload = safety_case(notice_type, reason)
+ requests: list[httpx2.Request] = []
+ async with AsyncOpenAI(
+ base_url=BASE_URL,
+ api_key="test-project-key",
+ admin_api_key="test-admin-key",
+ http_client=httpx2.AsyncClient(transport=httpx2.MockTransport(response_handler(payload, requests))),
+ _strict_response_validation=True,
+ ) as client:
+ result = await client.safety.cases.retrieve(
+ CASE_ID,
+ extra_headers={"X-Case-Trace": "caller-owned"},
+ extra_query={"trace": "contract"},
+ )
+
+ assert isinstance(result, SafetyCase)
+ assert result.to_dict() == payload
+ assert_request(requests)
+
+
+def test_safety_case_future_notice_is_preserved_by_default() -> None:
+ payload = safety_case("future-notice", None)
+ requests: list[httpx2.Request] = []
+ with OpenAI(
+ base_url=BASE_URL,
+ api_key="test-project-key",
+ admin_api_key="test-admin-key",
+ http_client=httpx2.Client(transport=httpx2.MockTransport(response_handler(payload, requests))),
+ ) as client:
+ result = client.safety.cases.retrieve(CASE_ID)
+
+ assert result.to_dict() == payload
+ assert len(requests) == 1
+ assert requests[0].headers["Authorization"] == "Bearer test-project-key"
+
+
+def test_sync_safety_case_never_uses_admin_key_as_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
+ requests: list[httpx2.Request] = []
+ with OpenAI(
+ base_url=BASE_URL,
+ api_key=None,
+ admin_api_key="test-admin-key",
+ http_client=httpx2.Client(
+ transport=httpx2.MockTransport(response_handler(safety_case("warning", None), requests))
+ ),
+ ) as client:
+ with pytest.raises(TypeError, match="Could not resolve authentication method"):
+ client.safety.cases.retrieve(CASE_ID)
+ assert not requests
+
+
+@pytest.mark.asyncio
+async def test_async_safety_case_never_uses_admin_key_as_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delenv("OPENAI_API_KEY", raising=False)
+ requests: list[httpx2.Request] = []
+ async with AsyncOpenAI(
+ base_url=BASE_URL,
+ api_key=None,
+ admin_api_key="test-admin-key",
+ http_client=httpx2.AsyncClient(
+ transport=httpx2.MockTransport(response_handler(safety_case("warning", None), requests))
+ ),
+ ) as client:
+ with pytest.raises(TypeError, match="Could not resolve authentication method"):
+ await client.safety.cases.retrieve(CASE_ID)
+ assert not requests