diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d52d2b9..3b07edf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.13.0" + ".": "0.13.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 37e3c10..68bc079 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/groqcloud%2Fgroqcloud-98874226a7dcad763f5fb96534de25a43f9187733a5293fa278b0b61bf71a9b3.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/groqcloud%2Fgroqcloud-a711d22c2283945608fc5ab6a399805da8961d6b9e0e068637d389ed611230c0.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index a780cb0..642eec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.13.1 (2024-12-14) + +Full Changelog: [v0.13.0...v0.13.1](https://github.com/groq/groq-python/compare/v0.13.0...v0.13.1) + +### Chores + +* **internal:** add support for TypeAliasType ([#153](https://github.com/groq/groq-python/issues/153)) ([c5ca4fc](https://github.com/groq/groq-python/commit/c5ca4fc7c1ced7e49f06d05e67924f702e5e021d)) +* **internal:** bump pydantic dependency ([#150](https://github.com/groq/groq-python/issues/150)) ([63e5754](https://github.com/groq/groq-python/commit/63e5754857bcbc137350971520cf9997e4d78da3)) +* **internal:** bump pyright ([#145](https://github.com/groq/groq-python/issues/145)) ([8f2b3a0](https://github.com/groq/groq-python/commit/8f2b3a0d70dad49b652a61f549979c1c5125d9d4)) +* **internal:** bump pyright ([#152](https://github.com/groq/groq-python/issues/152)) ([75476cd](https://github.com/groq/groq-python/commit/75476cd26c33e48220ab8760e129cd3f70413df5)) +* **internal:** codegen related update ([#154](https://github.com/groq/groq-python/issues/154)) ([db44bd6](https://github.com/groq/groq-python/commit/db44bd6adfb1eddfee9ee06cc63d4c594713eebc)) +* **internal:** codegen related update ([#155](https://github.com/groq/groq-python/issues/155)) ([7f303c3](https://github.com/groq/groq-python/commit/7f303c31dbebfe97b97388ff7f29238fc917da43)) +* **internal:** updated imports ([#156](https://github.com/groq/groq-python/issues/156)) ([3f41c12](https://github.com/groq/groq-python/commit/3f41c12bd36e13eba37d2bd7fd4f3f595bda4602)) +* make the `Omit` type public ([#147](https://github.com/groq/groq-python/issues/147)) ([0613ae1](https://github.com/groq/groq-python/commit/0613ae1e9f9dede7cb951d2d0591017894576b3a)) + + +### Documentation + +* **readme:** fix http client proxies example ([#151](https://github.com/groq/groq-python/issues/151)) ([b858c4f](https://github.com/groq/groq-python/commit/b858c4f742bdd7aae79f17981cb9d3b001ed009b)) + ## 0.13.0 (2024-11-28) Full Changelog: [v0.12.0...v0.13.0](https://github.com/groq/groq-python/compare/v0.12.0...v0.13.0) diff --git a/README.md b/README.md index 1a2e494..9c36bbb 100644 --- a/README.md +++ b/README.md @@ -335,18 +335,19 @@ can also get all the extra fields on the Pydantic model as a dict with You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: -- Support for proxies -- Custom transports +- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) +- Custom [transports](https://www.python-httpx.org/advanced/transports/) - Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality ```python +import httpx from groq import Groq, DefaultHttpxClient client = Groq( # Or use the `GROQ_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( - proxies="http://my.test.proxy.example.com", + proxy="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0"), ), ) diff --git a/pyproject.toml b/pyproject.toml index 236b8a1..478101e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "groq" -version = "0.13.0" +version = "0.13.1" description = "The official Python library for the groq API" dynamic = ["readme"] license = "Apache-2.0" @@ -10,7 +10,7 @@ authors = [ dependencies = [ "httpx>=0.23.0, <1", "pydantic>=1.9.0, <3", - "typing-extensions>=4.7, <5", + "typing-extensions>=4.10, <5", "anyio>=3.5.0, <5", "distro>=1.7.0, <2", "sniffio", diff --git a/requirements-dev.lock b/requirements-dev.lock index 5ac9e92..5b05eb3 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -62,13 +62,13 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest -pydantic==2.9.2 +pydantic==2.10.3 # via groq -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.380 +pyright==1.1.390 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 @@ -97,6 +97,7 @@ typing-extensions==4.12.2 # via mypy # via pydantic # via pydantic-core + # via pyright virtualenv==20.24.5 # via nox zipp==3.17.0 diff --git a/requirements.lock b/requirements.lock index d2a8ddb..3d7e46b 100644 --- a/requirements.lock +++ b/requirements.lock @@ -30,9 +30,9 @@ httpx==0.25.2 idna==3.4 # via anyio # via httpx -pydantic==2.9.2 +pydantic==2.10.3 # via groq -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic sniffio==1.3.0 # via anyio diff --git a/src/groq/__init__.py b/src/groq/__init__.py index 25f858e..07f77a6 100644 --- a/src/groq/__init__.py +++ b/src/groq/__init__.py @@ -1,7 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from . import types -from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path from ._client import Groq, Client, Stream, Timeout, AsyncGroq, Transport, AsyncClient, AsyncStream, RequestOptions from ._models import BaseModel @@ -36,6 +36,7 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "Omit", "GroqError", "APIError", "APIStatusError", diff --git a/src/groq/_client.py b/src/groq/_client.py index 87f6486..f8d0c19 100644 --- a/src/groq/_client.py +++ b/src/groq/_client.py @@ -8,7 +8,7 @@ import httpx -from . import resources, _exceptions +from . import _exceptions from ._qs import Querystring from ._types import ( NOT_GIVEN, @@ -24,6 +24,7 @@ get_async_library, ) from ._version import __version__ +from .resources import models, embeddings from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import GroqError, APIStatusError from ._base_client import ( @@ -31,25 +32,17 @@ SyncAPIClient, AsyncAPIClient, ) +from .resources.chat import chat +from .resources.audio import audio -__all__ = [ - "Timeout", - "Transport", - "ProxiesTypes", - "RequestOptions", - "resources", - "Groq", - "AsyncGroq", - "Client", - "AsyncClient", -] +__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Groq", "AsyncGroq", "Client", "AsyncClient"] class Groq(SyncAPIClient): - chat: resources.Chat - embeddings: resources.Embeddings - audio: resources.Audio - models: resources.Models + chat: chat.Chat + embeddings: embeddings.Embeddings + audio: audio.Audio + models: models.Models with_raw_response: GroqWithRawResponse with_streaming_response: GroqWithStreamedResponse @@ -107,10 +100,10 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.chat = resources.Chat(self) - self.embeddings = resources.Embeddings(self) - self.audio = resources.Audio(self) - self.models = resources.Models(self) + self.chat = chat.Chat(self) + self.embeddings = embeddings.Embeddings(self) + self.audio = audio.Audio(self) + self.models = models.Models(self) self.with_raw_response = GroqWithRawResponse(self) self.with_streaming_response = GroqWithStreamedResponse(self) @@ -220,10 +213,10 @@ def _make_status_error( class AsyncGroq(AsyncAPIClient): - chat: resources.AsyncChat - embeddings: resources.AsyncEmbeddings - audio: resources.AsyncAudio - models: resources.AsyncModels + chat: chat.AsyncChat + embeddings: embeddings.AsyncEmbeddings + audio: audio.AsyncAudio + models: models.AsyncModels with_raw_response: AsyncGroqWithRawResponse with_streaming_response: AsyncGroqWithStreamedResponse @@ -281,10 +274,10 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.chat = resources.AsyncChat(self) - self.embeddings = resources.AsyncEmbeddings(self) - self.audio = resources.AsyncAudio(self) - self.models = resources.AsyncModels(self) + self.chat = chat.AsyncChat(self) + self.embeddings = embeddings.AsyncEmbeddings(self) + self.audio = audio.AsyncAudio(self) + self.models = models.AsyncModels(self) self.with_raw_response = AsyncGroqWithRawResponse(self) self.with_streaming_response = AsyncGroqWithStreamedResponse(self) @@ -395,34 +388,34 @@ def _make_status_error( class GroqWithRawResponse: def __init__(self, client: Groq) -> None: - self.chat = resources.ChatWithRawResponse(client.chat) - self.embeddings = resources.EmbeddingsWithRawResponse(client.embeddings) - self.audio = resources.AudioWithRawResponse(client.audio) - self.models = resources.ModelsWithRawResponse(client.models) + self.chat = chat.ChatWithRawResponse(client.chat) + self.embeddings = embeddings.EmbeddingsWithRawResponse(client.embeddings) + self.audio = audio.AudioWithRawResponse(client.audio) + self.models = models.ModelsWithRawResponse(client.models) class AsyncGroqWithRawResponse: def __init__(self, client: AsyncGroq) -> None: - self.chat = resources.AsyncChatWithRawResponse(client.chat) - self.embeddings = resources.AsyncEmbeddingsWithRawResponse(client.embeddings) - self.audio = resources.AsyncAudioWithRawResponse(client.audio) - self.models = resources.AsyncModelsWithRawResponse(client.models) + self.chat = chat.AsyncChatWithRawResponse(client.chat) + self.embeddings = embeddings.AsyncEmbeddingsWithRawResponse(client.embeddings) + self.audio = audio.AsyncAudioWithRawResponse(client.audio) + self.models = models.AsyncModelsWithRawResponse(client.models) class GroqWithStreamedResponse: def __init__(self, client: Groq) -> None: - self.chat = resources.ChatWithStreamingResponse(client.chat) - self.embeddings = resources.EmbeddingsWithStreamingResponse(client.embeddings) - self.audio = resources.AudioWithStreamingResponse(client.audio) - self.models = resources.ModelsWithStreamingResponse(client.models) + self.chat = chat.ChatWithStreamingResponse(client.chat) + self.embeddings = embeddings.EmbeddingsWithStreamingResponse(client.embeddings) + self.audio = audio.AudioWithStreamingResponse(client.audio) + self.models = models.ModelsWithStreamingResponse(client.models) class AsyncGroqWithStreamedResponse: def __init__(self, client: AsyncGroq) -> None: - self.chat = resources.AsyncChatWithStreamingResponse(client.chat) - self.embeddings = resources.AsyncEmbeddingsWithStreamingResponse(client.embeddings) - self.audio = resources.AsyncAudioWithStreamingResponse(client.audio) - self.models = resources.AsyncModelsWithStreamingResponse(client.models) + self.chat = chat.AsyncChatWithStreamingResponse(client.chat) + self.embeddings = embeddings.AsyncEmbeddingsWithStreamingResponse(client.embeddings) + self.audio = audio.AsyncAudioWithStreamingResponse(client.audio) + self.models = models.AsyncModelsWithStreamingResponse(client.models) Client = Groq diff --git a/src/groq/_models.py b/src/groq/_models.py index 6cb469e..7a547ce 100644 --- a/src/groq/_models.py +++ b/src/groq/_models.py @@ -46,6 +46,7 @@ strip_not_given, extract_type_arg, is_annotated_type, + is_type_alias_type, strip_annotated_type, ) from ._compat import ( @@ -428,6 +429,8 @@ def construct_type(*, value: object, type_: object) -> object: # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): diff --git a/src/groq/_response.py b/src/groq/_response.py index 95a0361..d28f5ad 100644 --- a/src/groq/_response.py +++ b/src/groq/_response.py @@ -25,7 +25,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -126,9 +126,15 @@ def __repr__(self) -> str: ) def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + # unwrap `Annotated[T, ...]` -> `T` - if to and is_annotated_type(to): - to = extract_type_arg(to, 0) + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) if self._is_sse_stream: if to: @@ -164,18 +170,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), ), ) - cast_to = to if to is not None else self._cast_to - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - if cast_to is NoneType: return cast(R, None) diff --git a/src/groq/_types.py b/src/groq/_types.py index 453e1a0..49ec28a 100644 --- a/src/groq/_types.py +++ b/src/groq/_types.py @@ -192,10 +192,8 @@ def get(self, __key: str) -> str | None: ... StrBytesIntFloat = Union[str, bytes, int, float] # Note: copied from Pydantic -# https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49 -IncEx: TypeAlias = Union[ - Set[int], Set[str], Mapping[int, Union["IncEx", Literal[True]]], Mapping[str, Union["IncEx", Literal[True]]] -] +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] PostParser = Callable[[Any], Any] diff --git a/src/groq/_utils/__init__.py b/src/groq/_utils/__init__.py index a7cff3c..d4fda26 100644 --- a/src/groq/_utils/__init__.py +++ b/src/groq/_utils/__init__.py @@ -39,6 +39,7 @@ is_iterable_type as is_iterable_type, is_required_type as is_required_type, is_annotated_type as is_annotated_type, + is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, extract_type_var_from_base as extract_type_var_from_base, ) diff --git a/src/groq/_utils/_typing.py b/src/groq/_utils/_typing.py index c036991..278749b 100644 --- a/src/groq/_utils/_typing.py +++ b/src/groq/_utils/_typing.py @@ -1,8 +1,17 @@ from __future__ import annotations +import sys +import typing +import typing_extensions from typing import Any, TypeVar, Iterable, cast from collections import abc as _c_abc -from typing_extensions import Required, Annotated, get_args, get_origin +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) from .._types import InheritsGeneric from .._compat import is_union as _is_union @@ -36,6 +45,26 @@ def is_typevar(typ: type) -> bool: return type(typ) == TypeVar # type: ignore +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): diff --git a/src/groq/_version.py b/src/groq/_version.py index f34185a..f48453b 100644 --- a/src/groq/_version.py +++ b/src/groq/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "groq" -__version__ = "0.13.0" # x-release-please-version +__version__ = "0.13.1" # x-release-please-version diff --git a/tests/test_models.py b/tests/test_models.py index 439865c..87adf6e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,7 @@ import json from typing import Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal, Annotated +from typing_extensions import Literal, Annotated, TypeAliasType import pytest import pydantic @@ -828,3 +828,19 @@ class B(BaseModel): # if the discriminator details object stays the same between invocations then # we hit the cache assert UnionType.__discriminator__ is discriminator + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +def test_type_alias_type() -> None: + Alias = TypeAliasType("Alias", str) + + class Model(BaseModel): + alias: Alias + union: Union[int, Alias] + + m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.alias, str) + assert m.alias == "foo" + assert isinstance(m.union, str) + assert m.union == "bar" diff --git a/tests/utils.py b/tests/utils.py index e519958..17aa076 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -16,6 +16,7 @@ is_union_type, extract_type_arg, is_annotated_type, + is_type_alias_type, ) from groq._compat import PYDANTIC_V2, field_outer_type, get_model_fields from groq._models import BaseModel @@ -51,6 +52,9 @@ def assert_matches_type( path: list[str], allow_none: bool = False, ) -> None: + if is_type_alias_type(type_): + type_ = type_.__value__ + # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): type_ = extract_type_arg(type_, 0)