Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 1.6.0 #984

Merged
merged 10 commits into from
Dec 19, 2023
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.5.0"
".": "1.6.0"
}
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changelog

## 1.6.0 (2023-12-19)

Full Changelog: [v1.5.0...v1.6.0](https://github.com/openai/openai-python/compare/v1.5.0...v1.6.0)

### Features

* **api:** add additional instructions for runs ([#995](https://github.com/openai/openai-python/issues/995)) ([7bf9b75](https://github.com/openai/openai-python/commit/7bf9b75067905449e83e828c12eb384022cff6ca))


### Chores

* **cli:** fix typo in completions ([#985](https://github.com/openai/openai-python/issues/985)) ([d1e9e8f](https://github.com/openai/openai-python/commit/d1e9e8f24df366bb7b796c55a98247c025d229f5))
* **cli:** fix typo in completions ([#986](https://github.com/openai/openai-python/issues/986)) ([626bc34](https://github.com/openai/openai-python/commit/626bc34d82a7057bac99f8b556f9e5f60c261ee7))
* **internal:** fix binary response tests ([#983](https://github.com/openai/openai-python/issues/983)) ([cfb7e30](https://github.com/openai/openai-python/commit/cfb7e308393f2e912e959dd10d68096dd5b3ab9c))
* **internal:** fix typos ([#993](https://github.com/openai/openai-python/issues/993)) ([3b338a4](https://github.com/openai/openai-python/commit/3b338a401b206618774291ff8137deb0cc5f6b4c))
* **internal:** minor utils restructuring ([#992](https://github.com/openai/openai-python/issues/992)) ([5ba576a](https://github.com/openai/openai-python/commit/5ba576ae38d2c4c4d32a21933e0d68e0bc2f0d49))
* **package:** bump minimum typing-extensions to 4.7 ([#994](https://github.com/openai/openai-python/issues/994)) ([0c2da84](https://github.com/openai/openai-python/commit/0c2da84badf416f8b2213983f68bd2b6f9e52f2b))
* **streaming:** update constructor to use direct client names ([#991](https://github.com/openai/openai-python/issues/991)) ([6c3427d](https://github.com/openai/openai-python/commit/6c3427dac8c414658516aeb4caf5d5fd8b11097b))


### Documentation

* upgrade models in examples to latest version ([#989](https://github.com/openai/openai-python/issues/989)) ([cedd574](https://github.com/openai/openai-python/commit/cedd574e5611f3e71e92b523a72ba87bcfe546f1))

## 1.5.0 (2023-12-17)

Full Changelog: [v1.4.0...v1.5.0](https://github.com/openai/openai-python/compare/v1.4.0...v1.5.0)
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openai"
version = "1.5.0"
version = "1.6.0"
description = "The official Python library for the openai API"
readme = "README.md"
license = "Apache-2.0"
Expand All @@ -10,7 +10,7 @@ authors = [
dependencies = [
"httpx>=0.23.0, <1",
"pydantic>=1.9.0, <3",
"typing-extensions>=4.5, <5",
"typing-extensions>=4.7, <5",
"anyio>=3.5.0, <5",
"distro>=1.7.0, <2",
"sniffio",
Expand Down
2 changes: 1 addition & 1 deletion src/openai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@


class PageInfo:
"""Stores the necesary information to build the request to retrieve the next page.
"""Stores the necessary information to build the request to retrieve the next page.
Either `url` or `params` must be set.
"""
Expand Down
17 changes: 9 additions & 8 deletions src/openai/_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import datetime
import functools
from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast
from typing_extensions import Awaitable, ParamSpec, get_args, override, get_origin
from typing_extensions import Awaitable, ParamSpec, override, get_origin

import httpx

from ._types import NoneType, UnknownResponse, BinaryResponseContent
from ._utils import is_given
from ._utils import is_given, extract_type_var_from_base
from ._models import BaseModel, is_basemodel
from ._constants import RAW_RESPONSE_HEADER
from ._exceptions import APIResponseValidationError
Expand Down Expand Up @@ -221,12 +221,13 @@ def __init__(self) -> None:


def _extract_stream_chunk_type(stream_cls: type) -> type:
args = get_args(stream_cls)
if not args:
raise TypeError(
f"Expected stream_cls to have been given a generic type argument, e.g. Stream[Foo] but received {stream_cls}",
)
return cast(type, args[0])
from ._base_client import Stream, AsyncStream

return extract_type_var_from_base(
stream_cls,
index=0,
generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
)


def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
Expand Down
81 changes: 61 additions & 20 deletions src/openai/_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,52 @@
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any, Generic, Iterator, AsyncIterator
from typing_extensions import override
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast
from typing_extensions import Self, override

import httpx

from ._types import ResponseT
from ._utils import is_mapping
from ._exceptions import APIError

if TYPE_CHECKING:
from ._base_client import SyncAPIClient, AsyncAPIClient
from ._client import OpenAI, AsyncOpenAI


class Stream(Generic[ResponseT]):
_T = TypeVar("_T")


class Stream(Generic[_T]):
"""Provides the core interface to iterate over a synchronous stream response."""

response: httpx.Response

def __init__(
self,
*,
cast_to: type[ResponseT],
cast_to: type[_T],
response: httpx.Response,
client: SyncAPIClient,
client: OpenAI,
) -> None:
self.response = response
self._cast_to = cast_to
self._client = client
self._decoder = SSEDecoder()
self._iterator = self.__stream__()

def __next__(self) -> ResponseT:
def __next__(self) -> _T:
return self._iterator.__next__()

def __iter__(self) -> Iterator[ResponseT]:
def __iter__(self) -> Iterator[_T]:
for item in self._iterator:
yield item

def _iter_events(self) -> Iterator[ServerSentEvent]:
yield from self._decoder.iter(self.response.iter_lines())

def __stream__(self) -> Iterator[ResponseT]:
cast_to = self._cast_to
def __stream__(self) -> Iterator[_T]:
cast_to = cast(Any, self._cast_to)
response = self.response
process_data = self._client._process_response_data
iterator = self._iter_events()
Expand All @@ -57,7 +60,7 @@ def __stream__(self) -> Iterator[ResponseT]:
data = sse.json()
if is_mapping(data) and data.get("error"):
raise APIError(
message="An error ocurred during streaming",
message="An error occurred during streaming",
request=self.response.request,
body=data["error"],
)
Expand All @@ -68,38 +71,57 @@ def __stream__(self) -> Iterator[ResponseT]:
for _sse in iterator:
...

def __enter__(self) -> Self:
return self

def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
self.close()

def close(self) -> None:
"""
Close the response and release the connection.

Automatically called if the response body is read to completion.
"""
self.response.close()

class AsyncStream(Generic[ResponseT]):

class AsyncStream(Generic[_T]):
"""Provides the core interface to iterate over an asynchronous stream response."""

response: httpx.Response

def __init__(
self,
*,
cast_to: type[ResponseT],
cast_to: type[_T],
response: httpx.Response,
client: AsyncAPIClient,
client: AsyncOpenAI,
) -> None:
self.response = response
self._cast_to = cast_to
self._client = client
self._decoder = SSEDecoder()
self._iterator = self.__stream__()

async def __anext__(self) -> ResponseT:
async def __anext__(self) -> _T:
return await self._iterator.__anext__()

async def __aiter__(self) -> AsyncIterator[ResponseT]:
async def __aiter__(self) -> AsyncIterator[_T]:
async for item in self._iterator:
yield item

async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
async for sse in self._decoder.aiter(self.response.aiter_lines()):
yield sse

async def __stream__(self) -> AsyncIterator[ResponseT]:
cast_to = self._cast_to
async def __stream__(self) -> AsyncIterator[_T]:
cast_to = cast(Any, self._cast_to)
response = self.response
process_data = self._client._process_response_data
iterator = self._iter_events()
Expand All @@ -112,7 +134,7 @@ async def __stream__(self) -> AsyncIterator[ResponseT]:
data = sse.json()
if is_mapping(data) and data.get("error"):
raise APIError(
message="An error ocurred during streaming",
message="An error occurred during streaming",
request=self.response.request,
body=data["error"],
)
Expand All @@ -123,6 +145,25 @@ async def __stream__(self) -> AsyncIterator[ResponseT]:
async for _sse in iterator:
...

async def __aenter__(self) -> Self:
return self

async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
await self.close()

async def close(self) -> None:
"""
Close the response and release the connection.

Automatically called if the response body is read to completion.
"""
await self.response.aclose()


class ServerSentEvent:
def __init__(
Expand Down
14 changes: 14 additions & 0 deletions src/openai/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,17 @@ def get(self, __key: str) -> str | None:
IncEx: TypeAlias = "set[int] | set[str] | dict[int, Any] | dict[str, Any] | None"

PostParser = Callable[[Any], Any]


@runtime_checkable
class InheritsGeneric(Protocol):
"""Represents a type that has inherited from `Generic`
The `__orig_bases__` property can be used to determine the resolved
type variable for a given base class.
"""

__orig_bases__: tuple[_GenericAlias]


class _GenericAlias(Protocol):
__origin__: type[object]
15 changes: 9 additions & 6 deletions src/openai/_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,32 @@
from ._utils import parse_date as parse_date
from ._utils import is_sequence as is_sequence
from ._utils import coerce_float as coerce_float
from ._utils import is_list_type as is_list_type
from ._utils import is_mapping_t as is_mapping_t
from ._utils import removeprefix as removeprefix
from ._utils import removesuffix as removesuffix
from ._utils import extract_files as extract_files
from ._utils import is_sequence_t as is_sequence_t
from ._utils import is_union_type as is_union_type
from ._utils import required_args as required_args
from ._utils import coerce_boolean as coerce_boolean
from ._utils import coerce_integer as coerce_integer
from ._utils import file_from_path as file_from_path
from ._utils import parse_datetime as parse_datetime
from ._utils import strip_not_given as strip_not_given
from ._utils import deepcopy_minimal as deepcopy_minimal
from ._utils import extract_type_arg as extract_type_arg
from ._utils import is_required_type as is_required_type
from ._utils import get_async_library as get_async_library
from ._utils import is_annotated_type as is_annotated_type
from ._utils import maybe_coerce_float as maybe_coerce_float
from ._utils import get_required_header as get_required_header
from ._utils import maybe_coerce_boolean as maybe_coerce_boolean
from ._utils import maybe_coerce_integer as maybe_coerce_integer
from ._utils import strip_annotated_type as strip_annotated_type
from ._typing import is_list_type as is_list_type
from ._typing import is_union_type as is_union_type
from ._typing import extract_type_arg as extract_type_arg
from ._typing import is_required_type as is_required_type
from ._typing import is_annotated_type as is_annotated_type
from ._typing import strip_annotated_type as strip_annotated_type
from ._typing import extract_type_var_from_base as extract_type_var_from_base
from ._streams import consume_sync_iterator as consume_sync_iterator
from ._streams import consume_async_iterator as consume_async_iterator
from ._transform import PropertyInfo as PropertyInfo
from ._transform import transform as transform
from ._transform import maybe_transform as maybe_transform
12 changes: 12 additions & 0 deletions src/openai/_utils/_streams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from typing import Any
from typing_extensions import Iterator, AsyncIterator


def consume_sync_iterator(iterator: Iterator[Any]) -> None:
for _ in iterator:
...


async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
async for _ in iterator:
...
5 changes: 2 additions & 3 deletions src/openai/_utils/_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@

import pydantic

from ._utils import (
is_list,
is_mapping,
from ._utils import is_list, is_mapping
from ._typing import (
is_list_type,
is_union_type,
extract_type_arg,
Expand Down
Loading