Skip to content

Commit

Permalink
feat: OpenAPI spec update via Stainless API (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-app[bot] committed Mar 1, 2024
1 parent d9bdc57 commit 84ad1a4
Show file tree
Hide file tree
Showing 12 changed files with 54 additions and 223 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ name: CI
on:
push:
branches:
- stainless
- main
pull_request:
branches:
- stainless
- main

jobs:
lint:
Expand All @@ -14,7 +14,7 @@ jobs:


steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Install Rye
run: |
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ If you’d like to use the repository from source, you can either install from g
To install via git:

```bash
pip install git+ssh:https://[email protected]:groq/groq-python.git
pip install git+ssh:https://[email protected]/groq/groq-python.git
```

Alternatively, you can build from source and install the wheel file:
Expand All @@ -82,7 +82,7 @@ pip install ./path-to-wheel-file.whl

## Running tests

Most tests will require you to [setup a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.

```bash
# you will need npm installed
Expand Down Expand Up @@ -117,7 +117,7 @@ the changes aren't made through the automated pipeline, you may want to make rel

### Publish with a GitHub workflow

You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/groq/groq-python/actions/workflows/publish-pypi.yml). This will require a setup organization or repository secret to be set up.
You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/groq/groq-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.

### Publish manually

Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The REST API documentation can be found [on console.groq.com](https://console.gr
## Installation

```sh
# install from PyPI
pip install groq
```

Expand All @@ -38,7 +39,7 @@ chat_completion = client.chat.completions.create(
],
model="mixtral-8x7b-32768",
)
print(chat_completion.choices[0].message.content)
print(chat_completion.choices_0.message.content)
```

While you can provide an `api_key` keyword argument,
Expand Down Expand Up @@ -71,7 +72,7 @@ async def main() -> None:
],
model="mixtral-8x7b-32768",
)
print(chat_completion.choices[0].message.content)
print(chat_completion.choices_0.message.content)


asyncio.run(main())
Expand Down Expand Up @@ -261,9 +262,9 @@ completion = response.parse() # get the object that `chat.completions.create()`
print(completion.id)
```

These methods return an [`APIResponse`](https://github.com/groq/groq-python/tree/stainless/src/groq/_response.py) object.
These methods return an [`APIResponse`](https://github.com/groq/groq-python/tree/main/src/groq/_response.py) object.

The async client returns an [`AsyncAPIResponse`](https://github.com/groq/groq-python/tree/stainless/src/groq/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.
The async client returns an [`AsyncAPIResponse`](https://github.com/groq/groq-python/tree/main/src/groq/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.

#### `.with_streaming_response`

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pydantic==2.4.2
# via groq
pydantic-core==2.10.1
# via pydantic
pyright==1.1.332
pyright==1.1.351
pytest==7.1.1
# via pytest-asyncio
pytest-asyncio==0.21.1
Expand Down
5 changes: 4 additions & 1 deletion src/groq/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
RAW_RESPONSE_HEADER,
OVERRIDE_CAST_TO_HEADER,
)
from ._streaming import Stream, AsyncStream
from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
from ._exceptions import (
APIStatusError,
APITimeoutError,
Expand Down Expand Up @@ -430,6 +430,9 @@ def _prepare_url(self, url: str) -> URL:

return merge_url

def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
return SSEDecoder()

def _build_request(
self,
options: FinalRequestOptions,
Expand Down
2 changes: 1 addition & 1 deletion src/groq/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def construct_type(*, value: object, type_: type) -> object:

if is_union(origin):
try:
return validate_type(type_=type_, value=value)
return validate_type(type_=cast("type[object]", type_), value=value)
except Exception:
pass

Expand Down
5 changes: 3 additions & 2 deletions src/groq/_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from __future__ import annotations

import time
import asyncio
from typing import TYPE_CHECKING

import anyio

if TYPE_CHECKING:
from ._client import Groq, AsyncGroq

Expand Down Expand Up @@ -39,4 +40,4 @@ def __init__(self, client: AsyncGroq) -> None:
self._get_api_list = client.get_api_list

async def _sleep(self, seconds: float) -> None:
await asyncio.sleep(seconds)
await anyio.sleep(seconds)
38 changes: 28 additions & 10 deletions src/groq/_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import inspect
from types import TracebackType
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast
from typing_extensions import Self, TypeGuard, override, get_origin
from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable

import httpx

Expand All @@ -23,6 +23,8 @@ class Stream(Generic[_T]):

response: httpx.Response

_decoder: SSEDecoder | SSEBytesDecoder

def __init__(
self,
*,
Expand All @@ -33,7 +35,7 @@ def __init__(
self.response = response
self._cast_to = cast_to
self._client = client
self._decoder = SSEDecoder()
self._decoder = client._make_sse_decoder()
self._iterator = self.__stream__()

def __next__(self) -> _T:
Expand All @@ -44,7 +46,10 @@ def __iter__(self) -> Iterator[_T]:
yield item

def _iter_events(self) -> Iterator[ServerSentEvent]:
yield from self._decoder.iter(self.response.iter_lines())
if isinstance(self._decoder, SSEBytesDecoder):
yield from self._decoder.iter_bytes(self.response.iter_bytes())
else:
yield from self._decoder.iter(self.response.iter_lines())

def __stream__(self) -> Iterator[_T]:
cast_to = cast(Any, self._cast_to)
Expand All @@ -53,8 +58,6 @@ def __stream__(self) -> Iterator[_T]:
iterator = self._iter_events()

for sse in iterator:
if sse.data.startswith("[DONE]"):
break
yield process_data(data=sse.json(), cast_to=cast_to, response=response)

# Ensure the entire stream is consumed
Expand Down Expand Up @@ -86,6 +89,8 @@ class AsyncStream(Generic[_T]):

response: httpx.Response

_decoder: SSEDecoder | SSEBytesDecoder

def __init__(
self,
*,
Expand All @@ -96,7 +101,7 @@ def __init__(
self.response = response
self._cast_to = cast_to
self._client = client
self._decoder = SSEDecoder()
self._decoder = client._make_sse_decoder()
self._iterator = self.__stream__()

async def __anext__(self) -> _T:
Expand All @@ -107,10 +112,12 @@ async def __aiter__(self) -> AsyncIterator[_T]:
yield item

async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
async for sse in self._decoder.aiter(self.response.aiter_lines()):
if sse.data.startswith("[DONE]"):
break
yield sse
if isinstance(self._decoder, SSEBytesDecoder):
async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
yield sse
else:
async for sse in self._decoder.aiter(self.response.aiter_lines()):
yield sse

async def __stream__(self) -> AsyncIterator[_T]:
cast_to = cast(Any, self._cast_to)
Expand Down Expand Up @@ -263,6 +270,17 @@ def decode(self, line: str) -> ServerSentEvent | None:
return None


@runtime_checkable
class SSEBytesDecoder(Protocol):
def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
"""Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
...

def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
"""Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
...


def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
"""TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
origin = get_origin(typ) or typ
Expand Down
2 changes: 1 addition & 1 deletion src/groq/_utils/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def __dir__(self) -> Iterable[str]:

@property # type: ignore
@override
def __class__(self) -> type:
def __class__(self) -> type: # pyright: ignore
proxied = self.__get_proxied__()
if issubclass(type(proxied), LazyProxy):
return type(proxied)
Expand Down
1 change: 0 additions & 1 deletion src/groq/lib/chat_completion_chunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class ChoiceDelta(BaseModel):

function_call: Optional[ChoiceDeltaFunctionCall] = None


tool_calls: Optional[List[ChoiceDeltaToolCall]] = None


Expand Down
Loading

0 comments on commit 84ad1a4

Please sign in to comment.