Skip to content

Commit 6fc78a9

Browse files
docs: Update README. (#192)
1 parent 1a3af86 commit 6fc78a9

File tree

1 file changed

+100
-98
lines changed

1 file changed

+100
-98
lines changed

README.md

Lines changed: 100 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
[![PyPI version](https://img.shields.io/pypi/v/writer-sdk.svg)](https://pypi.org/project/writer-sdk/)
44

5-
The Writer Python library provides convenient access to the Writer REST API from any Python 3.8+
6-
application. The library includes type definitions for all request params and response fields,
7-
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
5+
The Writer Python library provides access to the Writer REST API from any Python 3.8+
6+
application. It includes a set of tools and utilities that make it easy to integrate the capabilities
7+
of Writer into your projects.
88

99
It is generated with [Stainless](https://www.stainlessapi.com/).
1010

@@ -14,23 +14,58 @@ The REST API documentation can be found on [dev.writer.com](https://dev.writer.c
1414

1515
## Installation
1616

17-
To install the package from PyPI, use the following command:
17+
To install the package from PyPI, use `pip`:
1818

1919
```sh
2020
pip install writer-sdk
2121
```
2222

23+
## Prequisites
24+
25+
Before you begin, ensure you have:
26+
27+
- Python 3.8 or higher
28+
- A [Writer API key](https://dev.writer.com/api-guides/introduction#authentication)
29+
30+
## Authentication
31+
32+
To authenticate with the Writer API, set the `WRITER_API_KEY` environment variable.
33+
34+
The `Writer` class automatically infers your API key from the `WRITER_API_KEY` environment variable.
35+
36+
```shell
37+
$ export WRITER_API_KEY="my-api-key"
38+
```
39+
40+
```python
41+
from writerai import Writer
42+
43+
client = Writer() # The API key will be inferred from the `WRITER_API_KEY` environment variable
44+
```
45+
46+
You can also explicitly set the API key with the `api_key` parameter:
47+
48+
```python
49+
from writerai import Writer
50+
51+
client = Writer(api_key="my-api-key")
52+
```
53+
54+
> Never hard-code your API keys in source code or commit them to version control systems like GitHub.
55+
> We recommend adding `WRITER_API_KEY="My API Key"` to your `.env` file so that your API Key is not stored in source control.
56+
2357
## Usage
2458

25-
The full API of this library can be found in [api.md](api.md).
59+
You can find the full API for this library in [api.md](api.md).
60+
61+
### Synchronous versus asynchronous usage
62+
63+
The Writer Python library supports both synchronous and asynchronous usage. With synchronous usage, you call the API methods directly:
2664

2765
```python
28-
import os
2966
from writerai import Writer
3067

31-
client = Writer(
32-
api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted
33-
)
68+
client = Writer()
3469

3570
chat_completion = client.chat.chat(
3671
messages=[
@@ -41,26 +76,16 @@ chat_completion = client.chat.chat(
4176
],
4277
model="palmyra-x-004",
4378
)
44-
print(chat_completion.id)
79+
print(chat.choices[0].message.content)
4580
```
4681

47-
While you can provide an `api_key` keyword argument,
48-
we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
49-
to add `WRITER_API_KEY="My API Key"` to your `.env` file
50-
so that your API Key is not stored in source control.
51-
52-
## Async usage
53-
54-
Simply import `AsyncWriter` instead of `Writer` and use `await` with each API call:
82+
With asynchronous usage, you import `AsyncWriter` instead of `Writer` and use `await` with each API call:
5583

5684
```python
57-
import os
5885
import asyncio
5986
from writerai import AsyncWriter
6087

61-
client = AsyncWriter(
62-
api_key=os.environ.get("WRITER_API_KEY"), # This is the default and can be omitted
63-
)
88+
client = AsyncWriter()
6489

6590

6691
async def main() -> None:
@@ -73,17 +98,19 @@ async def main() -> None:
7398
],
7499
model="palmyra-x-004",
75100
)
76-
print(chat_completion.id)
101+
print(chat.choices[0].message.content)
77102

78103

79104
asyncio.run(main())
80105
```
81106

82107
Functionality between the synchronous and asynchronous clients is otherwise identical.
83108

84-
## Streaming responses
109+
## Streaming versus non-streaming responses
110+
111+
The Writer Python library provides support for streaming responses using Server Side Events (SSE).
85112

86-
We provide support for streaming responses using Server Side Events (SSE).
113+
To use streaming, set the `stream` parameter to `True` when calling an API method. You can then iterate over the stream to get the response data:
87114

88115
```python
89116
from writerai import Writer
@@ -100,12 +127,15 @@ stream = client.chat.chat(
100127
model="palmyra-x-004",
101128
stream=True,
102129
)
103-
104-
for chat_completion in stream:
105-
print(chat_completion.id)
130+
output_text = ""
131+
for chunk in chat_response:
132+
if chunk.choices[0].delta.content:
133+
output_text += chunk.choices[0].delta.content:
134+
else:
135+
continue
106136
```
107137

108-
The async client uses the exact same interface.
138+
The async client uses the same interface.
109139

110140
```python
111141
import asyncio
@@ -123,18 +153,15 @@ stream = await client.chat.chat(
123153
model="palmyra-x-004",
124154
stream=True,
125155
)
126-
async for chat_completion in stream:
127-
print(chat_completion.id)
156+
output_text = ""
157+
async for chunk in stream:
158+
if chunk.choices[0].delta.content:
159+
output_text += chunk.choices[0].delta.content
160+
else:
161+
continue
128162
```
129163

130-
## Using types
131-
132-
Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like:
133-
134-
- Serializing back into JSON, `model.to_json()`
135-
- Converting to a dictionary, `model.to_dict()`
136-
137-
Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
164+
For non-streaming responses, the library returns a single response object.
138165

139166
## Pagination
140167

@@ -178,51 +205,30 @@ asyncio.run(main())
178205
Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
179206

180207
```python
181-
first_page = await client.graphs.list()
208+
first_page = await client.graphs.list() # Remove `await` for non-async usage.
182209
if first_page.has_next_page():
183210
print(f"will fetch next page using these details: {first_page.next_page_info()}")
184211
next_page = await first_page.get_next_page()
185212
print(f"number of items we just fetched: {len(next_page.data)}")
186-
187-
# Remove `await` for non-async usage.
188213
```
189214

190-
Or just work directly with the returned data:
215+
You can also work directly with the returned data:
191216

192217
```python
193-
first_page = await client.graphs.list()
218+
first_page = await client.graphs.list() # Remove `await` for non-async usage.
194219

195220
print(f"next page cursor: {first_page.after}") # => "next page cursor: ..."
196221
for graph in first_page.data:
197222
print(graph.id)
198-
199-
# Remove `await` for non-async usage.
200-
```
201-
202-
## File uploads
203-
204-
Request parameters that correspond to file uploads can be passed as `bytes`, a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`.
205-
206-
```python
207-
from pathlib import Path
208-
from writerai import Writer
209-
210-
client = Writer()
211-
212-
client.files.upload(
213-
content=Path("/path/to/file"),
214-
content_disposition="Content-Disposition",
215-
)
216223
```
217224

218-
The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically.
219-
220225
## Handling errors
221226

222-
When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `writerai.APIConnectionError` is raised.
227+
When the library is unable to connect to the API (for example, due to network connection problems, a timeout, or a firewall that doesn't allow the connection), a subclass of `writerai.APIConnectionError` is raised.
228+
229+
> If you are behind a firewall, you may need to configure it to allow connections to the Writer API at `https://api.writer.com/v1`.
223230
224-
When the API returns a non-success status code (that is, 4xx or 5xx
225-
response), a subclass of `writerai.APIStatusError` is raised, containing `status_code` and `response` properties.
231+
When the API returns a non-success status code - 4xx or 5xx - a subclass of `writerai.APIStatusError` is raised, containing `status_code` and `response` properties.
226232

227233
All errors inherit from `writerai.APIError`.
228234

@@ -268,9 +274,9 @@ Error codes are as follows:
268274

269275
### Retries
270276

271-
Certain errors are automatically retried 2 times by default, with a short exponential backoff.
272-
Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict,
273-
429 Rate Limit, and >=500 Internal errors are all retried by default.
277+
The library automatically retries certain errors two times by default, with a short exponential backoff.
278+
Connection errors (for example, due to a network connectivity problem), `408 Request Timeout`, `409 Conflict`,
279+
`429 Rate Limit`, and `>=500 Internal errors` are all retried by default.
274280

275281
You can use the `max_retries` option to configure or disable retry settings:
276282

@@ -283,7 +289,7 @@ client = Writer(
283289
max_retries=0,
284290
)
285291

286-
# Or, configure per-request:
292+
# Or, configure per request:
287293
client.with_options(max_retries=5).chat.chat(
288294
messages=[
289295
{
@@ -297,7 +303,7 @@ client.with_options(max_retries=5).chat.chat(
297303

298304
### Timeouts
299305

300-
By default requests time out after 3 minutes. You can configure this with a `timeout` option,
306+
By default, requests time out after three minutes. You can configure this with a `timeout` option,
301307
which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object:
302308

303309
```python
@@ -314,7 +320,7 @@ client = Writer(
314320
timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
315321
)
316322

317-
# Override per-request:
323+
# Override per request:
318324
client.with_options(timeout=5.0).chat.chat(
319325
messages=[
320326
{
@@ -330,11 +336,9 @@ On timeout, an `APITimeoutError` is thrown.
330336

331337
Note that requests that time out are [retried twice by default](#retries).
332338

333-
## Advanced
334-
335-
### Logging
339+
## Logging
336340

337-
We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module.
341+
We use the standard [`logging`](https://docs.python.org/3/library/logging.html) module.
338342

339343
You can enable logging by setting the environment variable `WRITER_LOG` to `info`.
340344

@@ -344,21 +348,25 @@ $ export WRITER_LOG=info
344348

345349
Or to `debug` for more verbose logging.
346350

351+
## Advanced
352+
347353
### How to tell whether `None` means `null` or missing
348354

349355
In an API response, a field may be explicitly `null`, or missing entirely; in either case, its value is `None` in this library. You can differentiate the two cases with `.model_fields_set`:
350356

351357
```py
352358
if response.my_field is None:
353359
if 'my_field' not in response.model_fields_set:
354-
print('Got json like {}, without a "my_field" key present at all.')
360+
print('Result was {}.')
355361
else:
356-
print('Got json like {"my_field": null}.')
362+
print('Result was{"my_field": null}.')
357363
```
358364

359365
### Accessing raw response data (e.g. headers)
360366

361-
The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g.,
367+
You can access the raw Response object by prefixing `.with_raw_response.` to any HTTP method call.
368+
369+
#### Non-streaming responses
362370

363371
```py
364372
from writerai import Writer
@@ -377,15 +385,13 @@ chat = response.parse() # get the object that `chat.chat()` would have returned
377385
print(chat.id)
378386
```
379387

380-
These methods return an [`APIResponse`](https://github.com/writer/writer-python/tree/main/src/writerai/_response.py) object.
388+
Calling a method with `.with_raw_response` returns an [`APIResponse`](https://github.com/writer/writer-python/tree/main/src/writerai/_response.py) object.
381389

382390
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.
383391

384-
#### `.with_streaming_response`
392+
#### Streaming responses
385393

386-
The above interface eagerly reads the full response body when you make the request, which may not always be what you want.
387-
388-
To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.
394+
To stream the raw response body, use `.with_streaming_response`, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods.
389395

390396
```python
391397
with client.chat.with_streaming_response.chat(
@@ -409,12 +415,12 @@ The context manager is required so that the response will reliably be closed.
409415

410416
This library is typed for convenient access to the documented API.
411417

412-
If you need to access undocumented endpoints, params, or response properties, the library can still be used.
418+
If you need to access undocumented endpoints, parameters, or response properties, you can still use the library.
413419

414420
#### Undocumented endpoints
415421

416-
To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other
417-
http verbs. Options on the client will be respected (such as retries) when making this request.
422+
To make requests to undocumented endpoints, use `client.get`, `client.post`, and other
423+
http verbs. Options on the client (such as retries) are respected when making these requests.
418424

419425
```py
420426
import httpx
@@ -428,9 +434,9 @@ response = client.post(
428434
print(response.headers.get("x-foo"))
429435
```
430436

431-
#### Undocumented request params
437+
#### Undocumented request parameters
432438

433-
If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request
439+
If you want to explicitly send an extra parameter, you can do so with the `extra_query`, `extra_body`, and `extra_headers` request
434440
options.
435441

436442
#### Undocumented response properties
@@ -469,7 +475,7 @@ client.with_options(http_client=DefaultHttpxClient(...))
469475

470476
### Managing HTTP resources
471477

472-
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.
478+
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.
473479

474480
```py
475481
from writerai import Writer
@@ -489,7 +495,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con
489495
2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
490496
3. Changes that we do not expect to impact the vast majority of users in practice.
491497

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

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

@@ -504,10 +510,6 @@ import writerai
504510
print(writerai.__version__)
505511
```
506512

507-
## Requirements
508-
509-
Python 3.8 or higher.
510-
511-
## Contributing
513+
## Feedback
512514

513-
See [the contributing documentation](./CONTRIBUTING.md).
515+
We welcome feedback! Please open an [issue](https://www.github.com/writer/writer-python/issues) with questions, bugs, or suggestions.

0 commit comments

Comments
 (0)