From f1b2b6458b47f1861bb7d297b007b6ee09123a85 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:43:49 +0000 Subject: [PATCH 01/11] chore(internal): bump pyright (#129) --- requirements-dev.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 0ba9283..5b2573d 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,7 +69,7 @@ pydantic-core==2.23.4 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.380 +pyright==1.1.389 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 # via writer-sdk virtualenv==20.24.5 # via nox From efadeb67a868c57775a1205a5765ccc9f45997e2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 00:16:12 +0000 Subject: [PATCH 02/11] feat(api): api update (#131) --- .stats.yml | 2 +- src/writerai/resources/applications.py | 24 +++++++++++++--- .../application_generate_content_params.py | 6 ++++ tests/api_resources/test_applications.py | 28 +++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 40f1ba4..dcd7ed2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-82683f2fd5f8778a27960ebabda40d6dc4640bdfb77ac4ec7f173b8bf8076d3c.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-fcd4d82943d0aeefc300520f0ee4684456ef647140f1d6ba9ffcb86278d83d3a.yml diff --git a/src/writerai/resources/applications.py b/src/writerai/resources/applications.py index a9f031f..acd3dae 100644 --- a/src/writerai/resources/applications.py +++ b/src/writerai/resources/applications.py @@ -51,6 +51,7 @@ def generate_content( application_id: str, *, inputs: Iterable[application_generate_content_params.Input], + stream: bool | NotGiven = NOT_GIVEN, # 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, @@ -59,9 +60,12 @@ def generate_content( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ApplicationGenerateContentResponse: """ - Generate content from an existing application with inputs. + Generate content from an existing no-code application with inputs. Args: + stream: Indicates whether the response should be streamed. Currently only supported for + research assistant applications. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -75,7 +79,11 @@ def generate_content( return self._post( f"/v1/applications/{application_id}", body=maybe_transform( - {"inputs": inputs}, application_generate_content_params.ApplicationGenerateContentParams + { + "inputs": inputs, + "stream": stream, + }, + application_generate_content_params.ApplicationGenerateContentParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -109,6 +117,7 @@ async def generate_content( application_id: str, *, inputs: Iterable[application_generate_content_params.Input], + stream: bool | NotGiven = NOT_GIVEN, # 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, @@ -117,9 +126,12 @@ async def generate_content( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ApplicationGenerateContentResponse: """ - Generate content from an existing application with inputs. + Generate content from an existing no-code application with inputs. Args: + stream: Indicates whether the response should be streamed. Currently only supported for + research assistant applications. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -133,7 +145,11 @@ async def generate_content( return await self._post( f"/v1/applications/{application_id}", body=await async_maybe_transform( - {"inputs": inputs}, application_generate_content_params.ApplicationGenerateContentParams + { + "inputs": inputs, + "stream": stream, + }, + application_generate_content_params.ApplicationGenerateContentParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout diff --git a/src/writerai/types/application_generate_content_params.py b/src/writerai/types/application_generate_content_params.py index 2b93046..1a1d933 100644 --- a/src/writerai/types/application_generate_content_params.py +++ b/src/writerai/types/application_generate_content_params.py @@ -11,6 +11,12 @@ class ApplicationGenerateContentParams(TypedDict, total=False): inputs: Required[Iterable[Input]] + stream: bool + """Indicates whether the response should be streamed. + + Currently only supported for research assistant applications. + """ + class Input(TypedDict, total=False): id: Required[str] diff --git a/tests/api_resources/test_applications.py b/tests/api_resources/test_applications.py index cdf9faa..7a93c41 100644 --- a/tests/api_resources/test_applications.py +++ b/tests/api_resources/test_applications.py @@ -30,6 +30,20 @@ def test_method_generate_content(self, client: Writer) -> None: ) assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) + @parametrize + def test_method_generate_content_with_all_params(self, client: Writer) -> None: + application = client.applications.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) + assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) + @parametrize def test_raw_response_generate_content(self, client: Writer) -> None: response = client.applications.with_raw_response.generate_content( @@ -96,6 +110,20 @@ async def test_method_generate_content(self, async_client: AsyncWriter) -> None: ) assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) + @parametrize + async def test_method_generate_content_with_all_params(self, async_client: AsyncWriter) -> None: + application = await async_client.applications.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) + assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) + @parametrize async def test_raw_response_generate_content(self, async_client: AsyncWriter) -> None: response = await async_client.applications.with_raw_response.generate_content( From c142caa6b2d646b270ec50c800c37effd9b90c49 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:26:16 +0000 Subject: [PATCH 03/11] feat(api): add streaming to application generation (#132) --- api.md | 2 +- src/writerai/resources/applications.py | 170 +++++++++++++++++- src/writerai/types/__init__.py | 1 + .../application_generate_content_chunk.py | 33 ++++ .../application_generate_content_params.py | 40 +++-- tests/api_resources/test_applications.py | 154 ++++++++++++++-- 6 files changed, 375 insertions(+), 25 deletions(-) create mode 100644 src/writerai/types/application_generate_content_chunk.py diff --git a/api.md b/api.md index a86244a..15740a8 100644 --- a/api.md +++ b/api.md @@ -3,7 +3,7 @@ Types: ```python -from writerai.types import ApplicationGenerateContentResponse +from writerai.types import ApplicationGenerateContentChunk, ApplicationGenerateContentResponse ``` Methods: diff --git a/src/writerai/resources/applications.py b/src/writerai/resources/applications.py index acd3dae..2f8afa3 100644 --- a/src/writerai/resources/applications.py +++ b/src/writerai/resources/applications.py @@ -3,12 +3,14 @@ from __future__ import annotations from typing import Iterable +from typing_extensions import Literal, overload import httpx from ..types import application_generate_content_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import ( + required_args, maybe_transform, async_maybe_transform, ) @@ -20,7 +22,9 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) +from .._streaming import Stream, AsyncStream from .._base_client import make_request_options +from ..types.application_generate_content_chunk import ApplicationGenerateContentChunk from ..types.application_generate_content_response import ApplicationGenerateContentResponse __all__ = ["ApplicationsResource", "AsyncApplicationsResource"] @@ -46,12 +50,13 @@ def with_streaming_response(self) -> ApplicationsResourceWithStreamingResponse: """ return ApplicationsResourceWithStreamingResponse(self) + @overload def generate_content( self, application_id: str, *, inputs: Iterable[application_generate_content_params.Input], - stream: bool | NotGiven = NOT_GIVEN, + stream: Literal[False] | NotGiven = NOT_GIVEN, # 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, @@ -74,6 +79,84 @@ def generate_content( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @overload + def generate_content( + self, + application_id: str, + *, + inputs: Iterable[application_generate_content_params.Input], + stream: Literal[True], + # 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 | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Stream[ApplicationGenerateContentChunk]: + """ + Generate content from an existing no-code application with inputs. + + Args: + stream: Indicates whether the response should be streamed. Currently only supported for + research assistant applications. + + 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 + """ + ... + + @overload + def generate_content( + self, + application_id: str, + *, + inputs: Iterable[application_generate_content_params.Input], + stream: bool, + # 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 | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ApplicationGenerateContentResponse | Stream[ApplicationGenerateContentChunk]: + """ + Generate content from an existing no-code application with inputs. + + Args: + stream: Indicates whether the response should be streamed. Currently only supported for + research assistant applications. + + 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 + """ + ... + + @required_args(["inputs"], ["inputs", "stream"]) + def generate_content( + self, + application_id: str, + *, + inputs: Iterable[application_generate_content_params.Input], + stream: Literal[False] | Literal[True] | NotGiven = NOT_GIVEN, + # 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 | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ApplicationGenerateContentResponse | Stream[ApplicationGenerateContentChunk]: if not application_id: raise ValueError(f"Expected a non-empty value for `application_id` but received {application_id!r}") return self._post( @@ -89,6 +172,8 @@ def generate_content( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=ApplicationGenerateContentResponse, + stream=stream or False, + stream_cls=Stream[ApplicationGenerateContentChunk], ) @@ -112,12 +197,13 @@ def with_streaming_response(self) -> AsyncApplicationsResourceWithStreamingRespo """ return AsyncApplicationsResourceWithStreamingResponse(self) + @overload async def generate_content( self, application_id: str, *, inputs: Iterable[application_generate_content_params.Input], - stream: bool | NotGiven = NOT_GIVEN, + stream: Literal[False] | NotGiven = NOT_GIVEN, # 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, @@ -140,6 +226,84 @@ async def generate_content( timeout: Override the client-level default timeout for this request, in seconds """ + ... + + @overload + async def generate_content( + self, + application_id: str, + *, + inputs: Iterable[application_generate_content_params.Input], + stream: Literal[True], + # 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 | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncStream[ApplicationGenerateContentChunk]: + """ + Generate content from an existing no-code application with inputs. + + Args: + stream: Indicates whether the response should be streamed. Currently only supported for + research assistant applications. + + 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 + """ + ... + + @overload + async def generate_content( + self, + application_id: str, + *, + inputs: Iterable[application_generate_content_params.Input], + stream: bool, + # 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 | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ApplicationGenerateContentResponse | AsyncStream[ApplicationGenerateContentChunk]: + """ + Generate content from an existing no-code application with inputs. + + Args: + stream: Indicates whether the response should be streamed. Currently only supported for + research assistant applications. + + 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 + """ + ... + + @required_args(["inputs"], ["inputs", "stream"]) + async def generate_content( + self, + application_id: str, + *, + inputs: Iterable[application_generate_content_params.Input], + stream: Literal[False] | Literal[True] | NotGiven = NOT_GIVEN, + # 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 | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ApplicationGenerateContentResponse | AsyncStream[ApplicationGenerateContentChunk]: if not application_id: raise ValueError(f"Expected a non-empty value for `application_id` but received {application_id!r}") return await self._post( @@ -155,6 +319,8 @@ async def generate_content( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=ApplicationGenerateContentResponse, + stream=stream or False, + stream_cls=AsyncStream[ApplicationGenerateContentChunk], ) diff --git a/src/writerai/types/__init__.py b/src/writerai/types/__init__.py index af6ad6c..cdbbd67 100644 --- a/src/writerai/types/__init__.py +++ b/src/writerai/types/__init__.py @@ -28,6 +28,7 @@ from .tool_parse_pdf_response import ToolParsePdfResponse as ToolParsePdfResponse from .completion_create_params import CompletionCreateParams as CompletionCreateParams from .graph_add_file_to_graph_params import GraphAddFileToGraphParams as GraphAddFileToGraphParams +from .application_generate_content_chunk import ApplicationGenerateContentChunk as ApplicationGenerateContentChunk from .application_generate_content_params import ApplicationGenerateContentParams as ApplicationGenerateContentParams from .tool_context_aware_splitting_params import ToolContextAwareSplittingParams as ToolContextAwareSplittingParams from .application_generate_content_response import ( diff --git a/src/writerai/types/application_generate_content_chunk.py b/src/writerai/types/application_generate_content_chunk.py new file mode 100644 index 0000000..70e9693 --- /dev/null +++ b/src/writerai/types/application_generate_content_chunk.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["ApplicationGenerateContentChunk", "Delta", "DeltaStage"] + + +class DeltaStage(BaseModel): + id: str + """The unique identifier for the stage.""" + + content: str + """The text content of the stage.""" + + sources: Optional[List[str]] = None + """A list of sources (URLs) that that stage used to process that particular step.""" + + +class Delta(BaseModel): + content: Optional[str] = None + """The main text output.""" + + stages: Optional[List[DeltaStage]] = None + """A list of stages that show the 'thinking process'.""" + + title: Optional[str] = None + """The name of the output.""" + + +class ApplicationGenerateContentChunk(BaseModel): + delta: Delta diff --git a/src/writerai/types/application_generate_content_params.py b/src/writerai/types/application_generate_content_params.py index 1a1d933..885a2be 100644 --- a/src/writerai/types/application_generate_content_params.py +++ b/src/writerai/types/application_generate_content_params.py @@ -2,21 +2,20 @@ from __future__ import annotations -from typing import List, Iterable -from typing_extensions import Required, TypedDict +from typing import List, Union, Iterable +from typing_extensions import Literal, Required, TypedDict -__all__ = ["ApplicationGenerateContentParams", "Input"] +__all__ = [ + "ApplicationGenerateContentParamsBase", + "Input", + "ApplicationGenerateContentParamsNonStreaming", + "ApplicationGenerateContentParamsStreaming", +] -class ApplicationGenerateContentParams(TypedDict, total=False): +class ApplicationGenerateContentParamsBase(TypedDict, total=False): inputs: Required[Iterable[Input]] - stream: bool - """Indicates whether the response should be streamed. - - Currently only supported for research assistant applications. - """ - class Input(TypedDict, total=False): id: Required[str] @@ -34,3 +33,24 @@ class Input(TypedDict, total=False): [here](https://dev.writer.com/api-guides/api-reference/file-api/upload-files) for the Files API. """ + + +class ApplicationGenerateContentParamsNonStreaming(ApplicationGenerateContentParamsBase, total=False): + stream: Literal[False] + """Indicates whether the response should be streamed. + + Currently only supported for research assistant applications. + """ + + +class ApplicationGenerateContentParamsStreaming(ApplicationGenerateContentParamsBase): + stream: Required[Literal[True]] + """Indicates whether the response should be streamed. + + Currently only supported for research assistant applications. + """ + + +ApplicationGenerateContentParams = Union[ + ApplicationGenerateContentParamsNonStreaming, ApplicationGenerateContentParamsStreaming +] diff --git a/tests/api_resources/test_applications.py b/tests/api_resources/test_applications.py index 7a93c41..6d5e45a 100644 --- a/tests/api_resources/test_applications.py +++ b/tests/api_resources/test_applications.py @@ -18,7 +18,7 @@ class TestApplications: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - def test_method_generate_content(self, client: Writer) -> None: + def test_method_generate_content_overload_1(self, client: Writer) -> None: application = client.applications.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -31,7 +31,7 @@ def test_method_generate_content(self, client: Writer) -> None: assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) @parametrize - def test_method_generate_content_with_all_params(self, client: Writer) -> None: + def test_method_generate_content_with_all_params_overload_1(self, client: Writer) -> None: application = client.applications.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -40,12 +40,12 @@ def test_method_generate_content_with_all_params(self, client: Writer) -> None: "value": ["string"], } ], - stream=True, + stream=False, ) assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) @parametrize - def test_raw_response_generate_content(self, client: Writer) -> None: + def test_raw_response_generate_content_overload_1(self, client: Writer) -> None: response = client.applications.with_raw_response.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -62,7 +62,7 @@ def test_raw_response_generate_content(self, client: Writer) -> None: assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) @parametrize - def test_streaming_response_generate_content(self, client: Writer) -> None: + def test_streaming_response_generate_content_overload_1(self, client: Writer) -> None: with client.applications.with_streaming_response.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -81,7 +81,71 @@ def test_streaming_response_generate_content(self, client: Writer) -> None: assert cast(Any, response.is_closed) is True @parametrize - def test_path_params_generate_content(self, client: Writer) -> None: + def test_path_params_generate_content_overload_1(self, client: Writer) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `application_id` but received ''"): + client.applications.with_raw_response.generate_content( + application_id="", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + ) + + @parametrize + def test_method_generate_content_overload_2(self, client: Writer) -> None: + application_stream = client.applications.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) + application_stream.response.close() + + @parametrize + def test_raw_response_generate_content_overload_2(self, client: Writer) -> None: + response = client.applications.with_raw_response.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_generate_content_overload_2(self, client: Writer) -> None: + with client.applications.with_streaming_response.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_generate_content_overload_2(self, client: Writer) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `application_id` but received ''"): client.applications.with_raw_response.generate_content( application_id="", @@ -91,6 +155,7 @@ def test_path_params_generate_content(self, client: Writer) -> None: "value": ["string"], } ], + stream=True, ) @@ -98,7 +163,7 @@ class TestAsyncApplications: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_generate_content(self, async_client: AsyncWriter) -> None: + async def test_method_generate_content_overload_1(self, async_client: AsyncWriter) -> None: application = await async_client.applications.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -111,7 +176,7 @@ async def test_method_generate_content(self, async_client: AsyncWriter) -> None: assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) @parametrize - async def test_method_generate_content_with_all_params(self, async_client: AsyncWriter) -> None: + async def test_method_generate_content_with_all_params_overload_1(self, async_client: AsyncWriter) -> None: application = await async_client.applications.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -120,12 +185,12 @@ async def test_method_generate_content_with_all_params(self, async_client: Async "value": ["string"], } ], - stream=True, + stream=False, ) assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) @parametrize - async def test_raw_response_generate_content(self, async_client: AsyncWriter) -> None: + async def test_raw_response_generate_content_overload_1(self, async_client: AsyncWriter) -> None: response = await async_client.applications.with_raw_response.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -142,7 +207,7 @@ async def test_raw_response_generate_content(self, async_client: AsyncWriter) -> assert_matches_type(ApplicationGenerateContentResponse, application, path=["response"]) @parametrize - async def test_streaming_response_generate_content(self, async_client: AsyncWriter) -> None: + async def test_streaming_response_generate_content_overload_1(self, async_client: AsyncWriter) -> None: async with async_client.applications.with_streaming_response.generate_content( application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", inputs=[ @@ -161,7 +226,71 @@ async def test_streaming_response_generate_content(self, async_client: AsyncWrit assert cast(Any, response.is_closed) is True @parametrize - async def test_path_params_generate_content(self, async_client: AsyncWriter) -> None: + async def test_path_params_generate_content_overload_1(self, async_client: AsyncWriter) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `application_id` but received ''"): + await async_client.applications.with_raw_response.generate_content( + application_id="", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + ) + + @parametrize + async def test_method_generate_content_overload_2(self, async_client: AsyncWriter) -> None: + application_stream = await async_client.applications.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) + await application_stream.response.aclose() + + @parametrize + async def test_raw_response_generate_content_overload_2(self, async_client: AsyncWriter) -> None: + response = await async_client.applications.with_raw_response.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) + + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stream = await response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_generate_content_overload_2(self, async_client: AsyncWriter) -> None: + async with async_client.applications.with_streaming_response.generate_content( + application_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + inputs=[ + { + "id": "id", + "value": ["string"], + } + ], + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_generate_content_overload_2(self, async_client: AsyncWriter) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `application_id` but received ''"): await async_client.applications.with_raw_response.generate_content( application_id="", @@ -171,4 +300,5 @@ async def test_path_params_generate_content(self, async_client: AsyncWriter) -> "value": ["string"], } ], + stream=True, ) From 94b63bbc992974c0f794dcfe4ced4d7fb2284c50 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 02:13:17 +0000 Subject: [PATCH 04/11] chore: make the `Omit` type public (#133) --- src/writerai/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/writerai/__init__.py b/src/writerai/__init__.py index cc6410c..77e4947 100644 --- a/src/writerai/__init__.py +++ b/src/writerai/__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 Client, Stream, Writer, Timeout, Transport, AsyncClient, AsyncStream, AsyncWriter, RequestOptions from ._models import BaseModel @@ -36,6 +36,7 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "Omit", "WriterError", "APIError", "APIStatusError", From eb1ebfe452164409a68fc7c6d83d5fdd9ce3b089 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:37:55 +0000 Subject: [PATCH 05/11] chore(internal): bump pydantic dependency (#134) --- requirements-dev.lock | 4 ++-- requirements.lock | 4 ++-- src/writerai/_types.py | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 5b2573d..2e82a63 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -63,9 +63,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest -pydantic==2.9.2 +pydantic==2.10.3 # via writer-sdk -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich diff --git a/requirements.lock b/requirements.lock index 7189493..e010472 100644 --- a/requirements.lock +++ b/requirements.lock @@ -31,9 +31,9 @@ httpx==0.25.2 idna==3.4 # via anyio # via httpx -pydantic==2.9.2 +pydantic==2.10.3 # via writer-sdk -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic sniffio==1.3.0 # via anyio diff --git a/src/writerai/_types.py b/src/writerai/_types.py index 20b15b8..1b0929e 100644 --- a/src/writerai/_types.py +++ b/src/writerai/_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] From 4e39cfec407d89fcbe03e8283e7c7bc499bc7b40 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:17:18 +0000 Subject: [PATCH 06/11] docs(readme): fix http client proxies example (#135) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c087351..61fb90c 100644 --- a/README.md +++ b/README.md @@ -384,18 +384,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 writerai import Writer, DefaultHttpxClient client = Writer( # Or use the `WRITER_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"), ), ) From 66af39233901307387129f1eb95859434ad18760 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:24:14 +0000 Subject: [PATCH 07/11] chore(internal): bump pyright (#136) --- requirements-dev.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 2e82a63..9229417 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -69,7 +69,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.389 +pyright==1.1.390 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 From 4523c9dc88771fce1b330fcf57a4499af7606c44 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:37:07 +0000 Subject: [PATCH 08/11] chore(internal): add support for TypeAliasType (#137) --- pyproject.toml | 2 +- src/writerai/_models.py | 3 +++ src/writerai/_response.py | 20 ++++++++++---------- src/writerai/_utils/__init__.py | 1 + src/writerai/_utils/_typing.py | 31 ++++++++++++++++++++++++++++++- tests/test_models.py | 18 +++++++++++++++++- tests/utils.py | 4 ++++ 7 files changed, 66 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0fd9458..50e8872 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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/src/writerai/_models.py b/src/writerai/_models.py index b69ce6b..3518d71 100644 --- a/src/writerai/_models.py +++ b/src/writerai/_models.py @@ -47,6 +47,7 @@ strip_not_given, extract_type_arg, is_annotated_type, + is_type_alias_type, strip_annotated_type, ) from ._compat import ( @@ -429,6 +430,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/writerai/_response.py b/src/writerai/_response.py index 3a1356d..a7f0b0c 100644 --- a/src/writerai/_response.py +++ b/src/writerai/_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/writerai/_utils/__init__.py b/src/writerai/_utils/__init__.py index a7cff3c..d4fda26 100644 --- a/src/writerai/_utils/__init__.py +++ b/src/writerai/_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/writerai/_utils/_typing.py b/src/writerai/_utils/_typing.py index c036991..278749b 100644 --- a/src/writerai/_utils/_typing.py +++ b/src/writerai/_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/tests/test_models.py b/tests/test_models.py index 2d00c35..4678805 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 741b4af..64664db 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 writerai._compat import PYDANTIC_V2, field_outer_type, get_model_fields from writerai._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) From fc58a7de32363b013cc390ebc7c237cf74fa4137 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:58:56 +0000 Subject: [PATCH 09/11] chore(internal): updated imports (#138) --- src/writerai/_client.py | 128 +++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/src/writerai/_client.py b/src/writerai/_client.py index 0cbecbb..8585f5b 100644 --- a/src/writerai/_client.py +++ b/src/writerai/_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 chat, files, graphs, models, completions, applications from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import WriterError, APIStatusError from ._base_client import ( @@ -31,28 +32,19 @@ SyncAPIClient, AsyncAPIClient, ) +from .resources.tools import tools -__all__ = [ - "Timeout", - "Transport", - "ProxiesTypes", - "RequestOptions", - "resources", - "Writer", - "AsyncWriter", - "Client", - "AsyncClient", -] +__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Writer", "AsyncWriter", "Client", "AsyncClient"] class Writer(SyncAPIClient): - applications: resources.ApplicationsResource - chat: resources.ChatResource - completions: resources.CompletionsResource - models: resources.ModelsResource - graphs: resources.GraphsResource - files: resources.FilesResource - tools: resources.ToolsResource + applications: applications.ApplicationsResource + chat: chat.ChatResource + completions: completions.CompletionsResource + models: models.ModelsResource + graphs: graphs.GraphsResource + files: files.FilesResource + tools: tools.ToolsResource with_raw_response: WriterWithRawResponse with_streaming_response: WriterWithStreamedResponse @@ -112,13 +104,13 @@ def __init__( self._default_stream_cls = Stream - self.applications = resources.ApplicationsResource(self) - self.chat = resources.ChatResource(self) - self.completions = resources.CompletionsResource(self) - self.models = resources.ModelsResource(self) - self.graphs = resources.GraphsResource(self) - self.files = resources.FilesResource(self) - self.tools = resources.ToolsResource(self) + self.applications = applications.ApplicationsResource(self) + self.chat = chat.ChatResource(self) + self.completions = completions.CompletionsResource(self) + self.models = models.ModelsResource(self) + self.graphs = graphs.GraphsResource(self) + self.files = files.FilesResource(self) + self.tools = tools.ToolsResource(self) self.with_raw_response = WriterWithRawResponse(self) self.with_streaming_response = WriterWithStreamedResponse(self) @@ -228,13 +220,13 @@ def _make_status_error( class AsyncWriter(AsyncAPIClient): - applications: resources.AsyncApplicationsResource - chat: resources.AsyncChatResource - completions: resources.AsyncCompletionsResource - models: resources.AsyncModelsResource - graphs: resources.AsyncGraphsResource - files: resources.AsyncFilesResource - tools: resources.AsyncToolsResource + applications: applications.AsyncApplicationsResource + chat: chat.AsyncChatResource + completions: completions.AsyncCompletionsResource + models: models.AsyncModelsResource + graphs: graphs.AsyncGraphsResource + files: files.AsyncFilesResource + tools: tools.AsyncToolsResource with_raw_response: AsyncWriterWithRawResponse with_streaming_response: AsyncWriterWithStreamedResponse @@ -294,13 +286,13 @@ def __init__( self._default_stream_cls = AsyncStream - self.applications = resources.AsyncApplicationsResource(self) - self.chat = resources.AsyncChatResource(self) - self.completions = resources.AsyncCompletionsResource(self) - self.models = resources.AsyncModelsResource(self) - self.graphs = resources.AsyncGraphsResource(self) - self.files = resources.AsyncFilesResource(self) - self.tools = resources.AsyncToolsResource(self) + self.applications = applications.AsyncApplicationsResource(self) + self.chat = chat.AsyncChatResource(self) + self.completions = completions.AsyncCompletionsResource(self) + self.models = models.AsyncModelsResource(self) + self.graphs = graphs.AsyncGraphsResource(self) + self.files = files.AsyncFilesResource(self) + self.tools = tools.AsyncToolsResource(self) self.with_raw_response = AsyncWriterWithRawResponse(self) self.with_streaming_response = AsyncWriterWithStreamedResponse(self) @@ -411,46 +403,46 @@ def _make_status_error( class WriterWithRawResponse: def __init__(self, client: Writer) -> None: - self.applications = resources.ApplicationsResourceWithRawResponse(client.applications) - self.chat = resources.ChatResourceWithRawResponse(client.chat) - self.completions = resources.CompletionsResourceWithRawResponse(client.completions) - self.models = resources.ModelsResourceWithRawResponse(client.models) - self.graphs = resources.GraphsResourceWithRawResponse(client.graphs) - self.files = resources.FilesResourceWithRawResponse(client.files) - self.tools = resources.ToolsResourceWithRawResponse(client.tools) + self.applications = applications.ApplicationsResourceWithRawResponse(client.applications) + self.chat = chat.ChatResourceWithRawResponse(client.chat) + self.completions = completions.CompletionsResourceWithRawResponse(client.completions) + self.models = models.ModelsResourceWithRawResponse(client.models) + self.graphs = graphs.GraphsResourceWithRawResponse(client.graphs) + self.files = files.FilesResourceWithRawResponse(client.files) + self.tools = tools.ToolsResourceWithRawResponse(client.tools) class AsyncWriterWithRawResponse: def __init__(self, client: AsyncWriter) -> None: - self.applications = resources.AsyncApplicationsResourceWithRawResponse(client.applications) - self.chat = resources.AsyncChatResourceWithRawResponse(client.chat) - self.completions = resources.AsyncCompletionsResourceWithRawResponse(client.completions) - self.models = resources.AsyncModelsResourceWithRawResponse(client.models) - self.graphs = resources.AsyncGraphsResourceWithRawResponse(client.graphs) - self.files = resources.AsyncFilesResourceWithRawResponse(client.files) - self.tools = resources.AsyncToolsResourceWithRawResponse(client.tools) + self.applications = applications.AsyncApplicationsResourceWithRawResponse(client.applications) + self.chat = chat.AsyncChatResourceWithRawResponse(client.chat) + self.completions = completions.AsyncCompletionsResourceWithRawResponse(client.completions) + self.models = models.AsyncModelsResourceWithRawResponse(client.models) + self.graphs = graphs.AsyncGraphsResourceWithRawResponse(client.graphs) + self.files = files.AsyncFilesResourceWithRawResponse(client.files) + self.tools = tools.AsyncToolsResourceWithRawResponse(client.tools) class WriterWithStreamedResponse: def __init__(self, client: Writer) -> None: - self.applications = resources.ApplicationsResourceWithStreamingResponse(client.applications) - self.chat = resources.ChatResourceWithStreamingResponse(client.chat) - self.completions = resources.CompletionsResourceWithStreamingResponse(client.completions) - self.models = resources.ModelsResourceWithStreamingResponse(client.models) - self.graphs = resources.GraphsResourceWithStreamingResponse(client.graphs) - self.files = resources.FilesResourceWithStreamingResponse(client.files) - self.tools = resources.ToolsResourceWithStreamingResponse(client.tools) + self.applications = applications.ApplicationsResourceWithStreamingResponse(client.applications) + self.chat = chat.ChatResourceWithStreamingResponse(client.chat) + self.completions = completions.CompletionsResourceWithStreamingResponse(client.completions) + self.models = models.ModelsResourceWithStreamingResponse(client.models) + self.graphs = graphs.GraphsResourceWithStreamingResponse(client.graphs) + self.files = files.FilesResourceWithStreamingResponse(client.files) + self.tools = tools.ToolsResourceWithStreamingResponse(client.tools) class AsyncWriterWithStreamedResponse: def __init__(self, client: AsyncWriter) -> None: - self.applications = resources.AsyncApplicationsResourceWithStreamingResponse(client.applications) - self.chat = resources.AsyncChatResourceWithStreamingResponse(client.chat) - self.completions = resources.AsyncCompletionsResourceWithStreamingResponse(client.completions) - self.models = resources.AsyncModelsResourceWithStreamingResponse(client.models) - self.graphs = resources.AsyncGraphsResourceWithStreamingResponse(client.graphs) - self.files = resources.AsyncFilesResourceWithStreamingResponse(client.files) - self.tools = resources.AsyncToolsResourceWithStreamingResponse(client.tools) + self.applications = applications.AsyncApplicationsResourceWithStreamingResponse(client.applications) + self.chat = chat.AsyncChatResourceWithStreamingResponse(client.chat) + self.completions = completions.AsyncCompletionsResourceWithStreamingResponse(client.completions) + self.models = models.AsyncModelsResourceWithStreamingResponse(client.models) + self.graphs = graphs.AsyncGraphsResourceWithStreamingResponse(client.graphs) + self.files = files.AsyncFilesResourceWithStreamingResponse(client.files) + self.tools = tools.AsyncToolsResourceWithStreamingResponse(client.tools) Client = Writer From 30ae8729b37d27572eafbd521e52eb114b635f8e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:47:50 +0000 Subject: [PATCH 10/11] docs(readme): example snippet for client context manager (#139) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 61fb90c..b3a7a86 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,16 @@ client.with_options(http_client=DefaultHttpxClient(...)) By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. +```py +from writerai import Writer + +with Writer() as client: + # make requests here + ... + +# HTTP client is now closed +``` + ## Versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: From aba04761e44b0e8a3b55b1782c44b2ea388e0978 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:48:10 +0000 Subject: [PATCH 11/11] release: 1.6.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ pyproject.toml | 2 +- src/writerai/_version.py | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fbd9082..7deae33 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.5.0" + ".": "1.6.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4acaa56..cfde535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 1.6.0 (2024-12-16) + +Full Changelog: [v1.5.0...v1.6.0](https://github.com/writer/writer-python/compare/v1.5.0...v1.6.0) + +### Features + +* **api:** add streaming to application generation ([#132](https://github.com/writer/writer-python/issues/132)) ([c142caa](https://github.com/writer/writer-python/commit/c142caa6b2d646b270ec50c800c37effd9b90c49)) +* **api:** api update ([#131](https://github.com/writer/writer-python/issues/131)) ([efadeb6](https://github.com/writer/writer-python/commit/efadeb67a868c57775a1205a5765ccc9f45997e2)) + + +### Chores + +* **internal:** add support for TypeAliasType ([#137](https://github.com/writer/writer-python/issues/137)) ([4523c9d](https://github.com/writer/writer-python/commit/4523c9dc88771fce1b330fcf57a4499af7606c44)) +* **internal:** bump pydantic dependency ([#134](https://github.com/writer/writer-python/issues/134)) ([eb1ebfe](https://github.com/writer/writer-python/commit/eb1ebfe452164409a68fc7c6d83d5fdd9ce3b089)) +* **internal:** bump pyright ([#129](https://github.com/writer/writer-python/issues/129)) ([f1b2b64](https://github.com/writer/writer-python/commit/f1b2b6458b47f1861bb7d297b007b6ee09123a85)) +* **internal:** bump pyright ([#136](https://github.com/writer/writer-python/issues/136)) ([66af392](https://github.com/writer/writer-python/commit/66af39233901307387129f1eb95859434ad18760)) +* **internal:** updated imports ([#138](https://github.com/writer/writer-python/issues/138)) ([fc58a7d](https://github.com/writer/writer-python/commit/fc58a7de32363b013cc390ebc7c237cf74fa4137)) +* make the `Omit` type public ([#133](https://github.com/writer/writer-python/issues/133)) ([94b63bb](https://github.com/writer/writer-python/commit/94b63bbc992974c0f794dcfe4ced4d7fb2284c50)) + + +### Documentation + +* **readme:** example snippet for client context manager ([#139](https://github.com/writer/writer-python/issues/139)) ([30ae872](https://github.com/writer/writer-python/commit/30ae8729b37d27572eafbd521e52eb114b635f8e)) +* **readme:** fix http client proxies example ([#135](https://github.com/writer/writer-python/issues/135)) ([4e39cfe](https://github.com/writer/writer-python/commit/4e39cfec407d89fcbe03e8283e7c7bc499bc7b40)) + ## 1.5.0 (2024-11-28) Full Changelog: [v1.4.0...v1.5.0](https://github.com/writer/writer-python/compare/v1.4.0...v1.5.0) diff --git a/pyproject.toml b/pyproject.toml index 50e8872..d4d7f6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "writer-sdk" -version = "1.5.0" +version = "1.6.0" description = "The official Python library for the writer API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/writerai/_version.py b/src/writerai/_version.py index 20200b4..9d2c545 100644 --- a/src/writerai/_version.py +++ b/src/writerai/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "writerai" -__version__ = "1.5.0" # x-release-please-version +__version__ = "1.6.0" # x-release-please-version