main
  1import json
  2from typing import Any, Union, cast
  3from typing_extensions import Annotated
  4
  5import httpx
  6import pytest
  7import pydantic
  8
  9from openai import OpenAI, BaseModel
 10from openai._streaming import Stream
 11from openai._base_client import FinalRequestOptions
 12from openai._legacy_response import LegacyAPIResponse
 13
 14from .utils import rich_print_str
 15
 16
 17class PydanticModel(pydantic.BaseModel): ...
 18
 19
 20def test_response_parse_mismatched_basemodel(client: OpenAI) -> None:
 21    response = LegacyAPIResponse(
 22        raw=httpx.Response(200, content=b"foo"),
 23        client=client,
 24        stream=False,
 25        stream_cls=None,
 26        cast_to=str,
 27        options=FinalRequestOptions.construct(method="get", url="/foo"),
 28    )
 29
 30    with pytest.raises(
 31        TypeError,
 32        match="Pydantic models must subclass our base model type, e.g. `from openai import BaseModel`",
 33    ):
 34        response.parse(to=PydanticModel)
 35
 36
 37@pytest.mark.parametrize(
 38    "content, expected",
 39    [
 40        ("false", False),
 41        ("true", True),
 42        ("False", False),
 43        ("True", True),
 44        ("TrUe", True),
 45        ("FalSe", False),
 46    ],
 47)
 48def test_response_parse_bool(client: OpenAI, content: str, expected: bool) -> None:
 49    response = LegacyAPIResponse(
 50        raw=httpx.Response(200, content=content),
 51        client=client,
 52        stream=False,
 53        stream_cls=None,
 54        cast_to=str,
 55        options=FinalRequestOptions.construct(method="get", url="/foo"),
 56    )
 57
 58    result = response.parse(to=bool)
 59    assert result is expected
 60
 61
 62def test_response_parse_custom_stream(client: OpenAI) -> None:
 63    response = LegacyAPIResponse(
 64        raw=httpx.Response(200, content=b"foo"),
 65        client=client,
 66        stream=True,
 67        stream_cls=None,
 68        cast_to=str,
 69        options=FinalRequestOptions.construct(method="get", url="/foo"),
 70    )
 71
 72    stream = response.parse(to=Stream[int])
 73    assert stream._cast_to == int
 74
 75
 76class CustomModel(BaseModel):
 77    foo: str
 78    bar: int
 79
 80
 81def test_response_parse_custom_model(client: OpenAI) -> None:
 82    response = LegacyAPIResponse(
 83        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
 84        client=client,
 85        stream=False,
 86        stream_cls=None,
 87        cast_to=str,
 88        options=FinalRequestOptions.construct(method="get", url="/foo"),
 89    )
 90
 91    obj = response.parse(to=CustomModel)
 92    assert obj.foo == "hello!"
 93    assert obj.bar == 2
 94
 95
 96def test_response_basemodel_request_id(client: OpenAI) -> None:
 97    response = LegacyAPIResponse(
 98        raw=httpx.Response(
 99            200,
100            headers={"x-request-id": "my-req-id"},
101            content=json.dumps({"foo": "hello!", "bar": 2}),
102        ),
103        client=client,
104        stream=False,
105        stream_cls=None,
106        cast_to=str,
107        options=FinalRequestOptions.construct(method="get", url="/foo"),
108    )
109
110    obj = response.parse(to=CustomModel)
111    assert obj._request_id == "my-req-id"
112    assert obj.foo == "hello!"
113    assert obj.bar == 2
114    assert obj.to_dict() == {"foo": "hello!", "bar": 2}
115    assert "_request_id" not in rich_print_str(obj)
116    assert "__exclude_fields__" not in rich_print_str(obj)
117
118
119def test_response_parse_annotated_type(client: OpenAI) -> None:
120    response = LegacyAPIResponse(
121        raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
122        client=client,
123        stream=False,
124        stream_cls=None,
125        cast_to=str,
126        options=FinalRequestOptions.construct(method="get", url="/foo"),
127    )
128
129    obj = response.parse(
130        to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
131    )
132    assert obj.foo == "hello!"
133    assert obj.bar == 2
134
135
136class OtherModel(pydantic.BaseModel):
137    a: str
138
139
140@pytest.mark.parametrize("client", [False], indirect=True)  # loose validation
141def test_response_parse_expect_model_union_non_json_content(client: OpenAI) -> None:
142    response = LegacyAPIResponse(
143        raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
144        client=client,
145        stream=False,
146        stream_cls=None,
147        cast_to=str,
148        options=FinalRequestOptions.construct(method="get", url="/foo"),
149    )
150
151    obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
152    assert isinstance(obj, str)
153    assert obj == "foo"