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: 0.2.0 #11

Merged
merged 8 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This workflow is triggered when a GitHub release is created.
# It can also be run manually to re-publish to PyPI in case it failed for some reason.
# You can run this workflow by navigating to https://www.github.com/writerai/writer-python/actions/workflows/publish-pypi.yml
# You can run this workflow by navigating to https://www.github.com/writer/writer-python/actions/workflows/publish-pypi.yml
name: Publish PyPI
on:
workflow_dispatch:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-doctor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
release_doctor:
name: release doctor
runs-on: ubuntu-latest
if: github.repository == 'writerai/writer-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
if: github.repository == 'writer/writer-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')

steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.prism.log
.vscode
_dev

Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.1.2"
".": "0.2.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
configured_endpoints: 3
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-387e688cfbf5098041d47c9c918c15d4978f98768b4daf901267aea8affc0a30.yml
configured_endpoints: 15
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-e54b835d1d64d37343017f4919c60998496a1b2b5c8e0c67af82093efc613f2f.yml
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.2.0 (2024-07-10)

Full Changelog: [v0.1.2...v0.2.0](https://github.com/writer/writer-python/compare/v0.1.2...v0.2.0)

### Features

* **api:** add support for graphs and files endpoints ([#15](https://github.com/writer/writer-python/issues/15)) ([173c935](https://github.com/writer/writer-python/commit/173c935d0bf8cde498e571ea4cc793994b002cc7))
* **api:** OpenAPI spec update via Stainless API ([#10](https://github.com/writer/writer-python/issues/10)) ([35aa63d](https://github.com/writer/writer-python/commit/35aa63dbd12cce5911e10671055dd1f914fbae64))
* **api:** update via SDK Studio ([#16](https://github.com/writer/writer-python/issues/16)) ([55beac6](https://github.com/writer/writer-python/commit/55beac6c92a74b2f90a5dcc10623c602d671ff03))
* **api:** update via SDK Studio ([#17](https://github.com/writer/writer-python/issues/17)) ([e098020](https://github.com/writer/writer-python/commit/e098020272202cad65f9233b238c156ca2383ec9))
* **api:** update via SDK Studio ([#18](https://github.com/writer/writer-python/issues/18)) ([ca78421](https://github.com/writer/writer-python/commit/ca78421478e6055d1147eb0bcafd88e4795c5c1f))
* Update completions_streaming.py ([764b30e](https://github.com/writer/writer-python/commit/764b30e52f6037bab4248d5f4fa2356435e2b9b2))
* Update completions_streaming.py ([00c67cc](https://github.com/writer/writer-python/commit/00c67ccacfe0d2955f00cd6f9f16a646ab2a5f22))

## 0.1.2 (2024-06-05)

Full Changelog: [v0.1.0...v0.1.2](https://github.com/writerai/writer-python/compare/v0.1.0...v0.1.2)
Expand Down
4 changes: 2 additions & 2 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://git@github.com/writerai/writer-python.git
pip install git+ssh://git@github.com/writer/writer-python.git
```

Alternatively, you can build from source and install the wheel file:
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/writerai/writer-python/actions/workflows/publish-pypi.yml). This requires 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/writer/writer-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up.

### Publish manually

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ chat = response.parse() # get the object that `chat.chat()` would have returned
print(chat.id)
```

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

The async client returns an [`AsyncAPIResponse`](https://github.com/writerai/writer-python/tree/main/src/writerai/_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/writer/writer-python/tree/main/src/writerai/_response.py) with the same structure, the only difference being `await`able methods for reading the response content.

#### `.with_streaming_response`

Expand Down Expand Up @@ -357,7 +357,7 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c

- Support for proxies
- Custom transports
- Additional [advanced](https://www.python-httpx.org/advanced/#client-instances) functionality
- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality

```python
from writerai import Writer, DefaultHttpxClient
Expand Down Expand Up @@ -386,7 +386,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con

We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.

We are keen for your feedback; please open an [issue](https://www.github.com/writerai/writer-python/issues) with questions, bugs, or suggestions.
We are keen for your feedback; please open an [issue](https://www.github.com/writer/writer-python/issues) with questions, bugs, or suggestions.

## Requirements

Expand Down
40 changes: 40 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,43 @@ from writerai.types import ModelListResponse
Methods:

- <code title="get /v1/models">client.models.<a href="./src/writerai/resources/models.py">list</a>() -> <a href="./src/writerai/types/model_list_response.py">ModelListResponse</a></code>

# Graphs

Types:

```python
from writerai.types import (
Graph,
GraphCreateResponse,
GraphUpdateResponse,
GraphDeleteResponse,
GraphRemoveFileFromGraphResponse,
)
```

Methods:

- <code title="post /v1/graphs">client.graphs.<a href="./src/writerai/resources/graphs.py">create</a>(\*\*<a href="src/writerai/types/graph_create_params.py">params</a>) -> <a href="./src/writerai/types/graph_create_response.py">GraphCreateResponse</a></code>
- <code title="get /v1/graphs/{graph_id}">client.graphs.<a href="./src/writerai/resources/graphs.py">retrieve</a>(graph_id) -> <a href="./src/writerai/types/graph.py">Graph</a></code>
- <code title="put /v1/graphs/{graph_id}">client.graphs.<a href="./src/writerai/resources/graphs.py">update</a>(graph_id, \*\*<a href="src/writerai/types/graph_update_params.py">params</a>) -> <a href="./src/writerai/types/graph_update_response.py">GraphUpdateResponse</a></code>
- <code title="get /v1/graphs">client.graphs.<a href="./src/writerai/resources/graphs.py">list</a>(\*\*<a href="src/writerai/types/graph_list_params.py">params</a>) -> <a href="./src/writerai/types/graph.py">SyncCursorPage[Graph]</a></code>
- <code title="delete /v1/graphs/{graph_id}">client.graphs.<a href="./src/writerai/resources/graphs.py">delete</a>(graph_id) -> <a href="./src/writerai/types/graph_delete_response.py">GraphDeleteResponse</a></code>
- <code title="post /v1/graphs/{graph_id}/file">client.graphs.<a href="./src/writerai/resources/graphs.py">add_file_to_graph</a>(graph_id, \*\*<a href="src/writerai/types/graph_add_file_to_graph_params.py">params</a>) -> <a href="./src/writerai/types/file.py">File</a></code>
- <code title="delete /v1/graphs/{graph_id}/file/{file_id}">client.graphs.<a href="./src/writerai/resources/graphs.py">remove_file_from_graph</a>(file_id, \*, graph_id) -> <a href="./src/writerai/types/graph_remove_file_from_graph_response.py">GraphRemoveFileFromGraphResponse</a></code>

# Files

Types:

```python
from writerai.types import File, FileDeleteResponse
```

Methods:

- <code title="get /v1/files/{fileId}">client.files.<a href="./src/writerai/resources/files.py">retrieve</a>(file_id) -> <a href="./src/writerai/types/file.py">File</a></code>
- <code title="get /v1/files">client.files.<a href="./src/writerai/resources/files.py">list</a>(\*\*<a href="src/writerai/types/file_list_params.py">params</a>) -> <a href="./src/writerai/types/file.py">SyncCursorPage[File]</a></code>
- <code title="delete /v1/files/{fileId}">client.files.<a href="./src/writerai/resources/files.py">delete</a>(file_id) -> <a href="./src/writerai/types/file_delete_response.py">FileDeleteResponse</a></code>
- <code title="get /v1/files/{fileId}/download">client.files.<a href="./src/writerai/resources/files.py">download</a>(file_id) -> BinaryAPIResponse</code>
- <code title="post /v1/files">client.files.<a href="./src/writerai/resources/files.py">upload</a>(\*\*<a href="src/writerai/types/file_upload_params.py">params</a>) -> <a href="./src/writerai/types/file.py">File</a></code>
3 changes: 3 additions & 0 deletions bin/publish-pypi
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
set -eux
mkdir -p dist
rye build --clean
# Patching importlib-metadata version until upstream library version is updated
# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
rye publish --yes --token=$PYPI_TOKEN
6 changes: 2 additions & 4 deletions examples/completions_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@
# $ rye sync --all-features
# $ WRITERAI_API_KEY="<your api key>" rye run python examples/completions_streaming.py

import random

from writerai import AsyncClient, BadRequestError


async def main() -> None:
client = AsyncClient()

print("Supported models:")
print("Available models:")
res = await client.models.list()
for model in res.models:
print(model.id)
Expand All @@ -27,7 +25,7 @@ async def main() -> None:
print(f"> {prompt}")
try:
stream_res = await client.completions.create(
model=random.choice(res.models).id,
model="palmyra-x-002-instruct",
prompt=prompt,
stream=True,
max_tokens=64,
Expand Down
23 changes: 19 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "writer-sdk"
version = "0.1.2"
version = "0.2.0"
description = "The official Python library for the writer API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down Expand Up @@ -39,8 +39,8 @@ classifiers = [


[project.urls]
Homepage = "https://github.com/writerai/writer-python"
Repository = "https://github.com/writerai/writer-python"
Homepage = "https://github.com/writer/writer-python"
Repository = "https://github.com/writer/writer-python"



Expand Down Expand Up @@ -99,6 +99,21 @@ include = [
[tool.hatch.build.targets.wheel]
packages = ["src/writerai"]

[tool.hatch.build.targets.sdist]
# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc)
include = [
"/*.toml",
"/*.json",
"/*.lock",
"/*.md",
"/mypy.ini",
"/noxfile.py",
"bin/*",
"examples/*",
"src/*",
"tests/*",
]

[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"

Expand All @@ -108,7 +123,7 @@ path = "README.md"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
# replace relative links with absolute links
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
replacement = '[\1](https://github.com/writerai/writer-python/tree/main/\g<2>)'
replacement = '[\1](https://github.com/writer/writer-python/tree/main/\g<2>)'

[tool.black]
line-length = 120
Expand Down
3 changes: 2 additions & 1 deletion requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
-e file:.
annotated-types==0.6.0
# via pydantic
anyio==4.1.0
anyio==4.4.0
# via httpx
# via writer-sdk
argcomplete==3.1.2
Expand Down Expand Up @@ -86,6 +86,7 @@ tomli==2.0.1
# via mypy
# via pytest
typing-extensions==4.8.0
# via anyio
# via mypy
# via pydantic
# via pydantic-core
Expand Down
3 changes: 2 additions & 1 deletion requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
-e file:.
annotated-types==0.6.0
# via pydantic
anyio==4.1.0
anyio==4.4.0
# via httpx
# via writer-sdk
certifi==2023.7.22
Expand Down Expand Up @@ -38,6 +38,7 @@ sniffio==1.3.0
# via httpx
# via writer-sdk
typing-extensions==4.8.0
# via anyio
# via pydantic
# via pydantic-core
# via writer-sdk
25 changes: 20 additions & 5 deletions src/writerai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
RequestOptions,
ModelBuilderProtocol,
)
from ._utils import is_dict, is_list, is_given, lru_cache, is_mapping
from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
from ._compat import model_copy, model_dump
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
from ._response import (
Expand Down Expand Up @@ -358,6 +358,7 @@ def __init__(
self._custom_query = custom_query or {}
self._strict_response_validation = _strict_response_validation
self._idempotency_header = None
self._platform: Platform | None = None

if max_retries is None: # pyright: ignore[reportUnnecessaryComparison]
raise TypeError(
Expand Down Expand Up @@ -456,7 +457,7 @@ def _build_request(
raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")

headers = self._build_headers(options)
params = _merge_mappings(self._custom_query, options.params)
params = _merge_mappings(self.default_query, options.params)
content_type = headers.get("Content-Type")

# If the given Content-Type header is multipart/form-data then it
Expand Down Expand Up @@ -592,6 +593,12 @@ def default_headers(self) -> dict[str, str | Omit]:
**self._custom_headers,
}

@property
def default_query(self) -> dict[str, object]:
return {
**self._custom_query,
}

def _validate_headers(
self,
headers: Headers, # noqa: ARG002
Expand All @@ -616,7 +623,10 @@ def base_url(self, url: URL | str) -> None:
self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))

def platform_headers(self) -> Dict[str, str]:
return platform_headers(self._version)
# the actual implementation is in a separate `lru_cache` decorated
# function because adding `lru_cache` to methods will leak memory
# https://github.com/python/cpython/issues/88476
return platform_headers(self._version, platform=self._platform)

def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
"""Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
Expand Down Expand Up @@ -1492,6 +1502,11 @@ async def _request(
stream_cls: type[_AsyncStreamT] | None,
remaining_retries: int | None,
) -> ResponseT | _AsyncStreamT:
if self._platform is None:
# `get_platform` can make blocking IO calls so we
# execute it earlier while we are in an async context
self._platform = await asyncify(get_platform)()

cast_to = self._maybe_override_cast_to(cast_to, options)
await self._prepare_options(options)

Expand Down Expand Up @@ -1915,11 +1930,11 @@ def get_platform() -> Platform:


@lru_cache(maxsize=None)
def platform_headers(version: str) -> Dict[str, str]:
def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]:
return {
"X-Stainless-Lang": "python",
"X-Stainless-Package-Version": version,
"X-Stainless-OS": str(get_platform()),
"X-Stainless-OS": str(platform or get_platform()),
"X-Stainless-Arch": str(get_architecture()),
"X-Stainless-Runtime": get_python_runtime(),
"X-Stainless-Runtime-Version": get_python_version(),
Expand Down
Loading