Skip to content
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 .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.3.2"
".": "2.3.3-rc1"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 33
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-4ec783072dd7f57c6e021a746df7650fb8d7a164d8ec25c7d5cab06c33bc114f.yml
openapi_spec_hash: ceab065d515f3681b0c33137da308968
config_hash: 089fd5502b9cf91247887b19117f1ca2
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-ea6ec4b34f6b7fdecc564f59b2e31482eee05830bf8dc1f389461b158de1548e.yml
openapi_spec_hash: ea89c1faed473908be2740efe6da255f
config_hash: 886645f89dc98f04b8931eaf02854e5f
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## 2.3.3-rc1 (2025-12-01)

Full Changelog: [v2.3.2...v2.3.3-rc1](https://github.com/writer/writer-python/compare/v2.3.2...v2.3.3-rc1)

### Bug Fixes

* **client:** close streams without requiring full consumption ([31e3903](https://github.com/writer/writer-python/commit/31e39034cab026c34c9509757a27d9e2221c0c5b))
* compat with Python 3.14 ([56db271](https://github.com/writer/writer-python/commit/56db2716054e1ba6a23071e172584e7c2433ba87))
* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([1fb3322](https://github.com/writer/writer-python/commit/1fb332284ab2c7ff87afeb686176df1efcf262db))
* ensure streams are always closed ([23c7971](https://github.com/writer/writer-python/commit/23c7971d69301956cef01d0041120a848818fa5a))


### Chores

* bump `httpx-aiohttp` version to 0.1.9 ([f2ef07d](https://github.com/writer/writer-python/commit/f2ef07dbe6ffd744bf58a6c7b5f3dac8b73a8805))
* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([74b4799](https://github.com/writer/writer-python/commit/74b479957daea7272bfd0a7533125b0bd42c17dd))
* **internal/tests:** avoid race condition with implicit client cleanup ([828ac4d](https://github.com/writer/writer-python/commit/828ac4d2a57d4f623d4fe2aef25390c5f0051b96))
* **internal:** codegen related update ([3b5b4a6](https://github.com/writer/writer-python/commit/3b5b4a69314e7c3853018233796b05a4035710fb))
* **internal:** detect missing future annotations with ruff ([9df4451](https://github.com/writer/writer-python/commit/9df44512304949e6193e7ff33390342e26d065c6))
* **internal:** grammar fix (it's -> its) ([e8b1113](https://github.com/writer/writer-python/commit/e8b11131528095f8acf847d126fda21cec0b66c6))
* **package:** drop Python 3.8 support ([9b204ce](https://github.com/writer/writer-python/commit/9b204ced5e50fa180e24b3d05ec271b8bbd7baff))


### Documentation

* **api:** updates to API spec ([04fe076](https://github.com/writer/writer-python/commit/04fe0769dcba588b421d5d6fe3fd5b7cf10a726d))

## 2.3.2 (2025-10-03)

Full Changelog: [v2.3.2-rc2...v2.3.2](https://github.com/writer/writer-python/compare/v2.3.2-rc2...v2.3.2)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- prettier-ignore -->
[![PyPI version](https://img.shields.io/pypi/v/writer-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/writer-sdk/)

The Writer Python library provides access to the Writer REST API from any Python 3.8+
The Writer Python library provides access to the Writer REST API from any Python 3.9+
application. It includes a set of tools and utilities that make it easy to integrate the capabilities
of Writer into your projects.

Expand All @@ -19,7 +19,7 @@ To install the package from PyPI, use `pip`:

```sh
# install from PyPI
pip install writer-sdk
pip install --pre writer-sdk
```

## Prequisites
Expand Down Expand Up @@ -116,7 +116,7 @@ You can enable this by installing `aiohttp`:

```sh
# install from PyPI
pip install writer-sdk[aiohttp]
pip install --pre writer-sdk[aiohttp]
```

Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
Expand Down
16 changes: 10 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "writer-sdk"
version = "2.3.2"
version = "2.3.3-rc1"
description = "The official Python library for the writer API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand All @@ -17,16 +17,16 @@ dependencies = [
"cached-property; python_version < '3.8'",
"jiter>=0.4.0, <1",
]
requires-python = ">= 3.8"
requires-python = ">= 3.9"
classifiers = [
"Typing :: Typed",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Operating System :: MacOS",
Expand All @@ -41,14 +41,14 @@ Homepage = "https://github.com/writer/writer-python"
Repository = "https://github.com/writer/writer-python"

[project.optional-dependencies]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]

[tool.rye]
managed = true
# version pins are in requirements-dev.lock
dev-dependencies = [
"pyright==1.1.399",
"mypy",
"mypy==1.17",
"respx",
"pytest",
"pytest-asyncio",
Expand Down Expand Up @@ -145,7 +145,7 @@ filterwarnings = [
# there are a couple of flags that are still disabled by
# default in strict mode as they are experimental and niche.
typeCheckingMode = "strict"
pythonVersion = "3.8"
pythonVersion = "3.9"

exclude = [
"_dev",
Expand Down Expand Up @@ -228,6 +228,8 @@ select = [
"B",
# remove unused imports
"F401",
# check for missing future annotations
"FA102",
# bare except statements
"E722",
# unused arguments
Expand All @@ -250,6 +252,8 @@ unfixable = [
"T203",
]

extend-safe-fixes = ["FA102"]

[tool.ruff.lint.flake8-tidy-imports.banned-api]
"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead"

Expand Down
6 changes: 4 additions & 2 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ httpx==0.28.1
# via httpx-aiohttp
# via respx
# via writer-sdk
httpx-aiohttp==0.1.8
httpx-aiohttp==0.1.9
# via writer-sdk
idna==3.4
# via anyio
Expand All @@ -79,7 +79,7 @@ mdurl==0.1.2
multidict==6.4.4
# via aiohttp
# via yarl
mypy==1.14.1
mypy==1.17.0
mypy-extensions==1.0.0
# via mypy
nest-asyncio==1.6.0
Expand All @@ -89,6 +89,8 @@ nox==2023.4.22
packaging==23.2
# via nox
# via pytest
pathspec==0.12.1
# via mypy
platformdirs==3.11.0
# via virtualenv
pluggy==1.5.0
Expand Down
10 changes: 5 additions & 5 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ httpcore==1.0.9
httpx==0.28.1
# via httpx-aiohttp
# via writer-sdk
httpx-aiohttp==0.1.8
httpx-aiohttp==0.1.9
# via writer-sdk
idna==3.4
# via anyio
Expand All @@ -57,21 +57,21 @@ multidict==6.4.4
propcache==0.3.1
# via aiohttp
# via yarl
pydantic==2.11.9
pydantic==2.12.5
# via writer-sdk
pydantic-core==2.33.2
pydantic-core==2.41.5
# via pydantic
sniffio==1.3.0
# via anyio
# via writer-sdk
typing-extensions==4.12.2
typing-extensions==4.15.0
# via anyio
# via multidict
# via pydantic
# via pydantic-core
# via typing-inspection
# via writer-sdk
typing-inspection==0.4.1
typing-inspection==0.4.2
# via pydantic
yarl==1.20.0
# via aiohttp
52 changes: 37 additions & 15 deletions src/writerai/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import inspect
import weakref
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from datetime import date, datetime
from typing_extensions import (
Expand Down Expand Up @@ -257,32 +258,41 @@ def model_dump(
mode: Literal["json", "python"] | str = "python",
include: IncEx | None = None,
exclude: IncEx | None = None,
context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
context: dict[str, Any] | None = None,
serialize_as_any: bool = False,
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
) -> dict[str, Any]:
"""Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump

Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.

Args:
mode: The mode in which `to_python` should run.
If mode is 'json', the dictionary will only contain JSON serializable types.
If mode is 'python', the dictionary may contain any Python objects.
include: A list of fields to include in the output.
exclude: A list of fields to exclude from the output.
If mode is 'json', the output will only contain JSON serializable types.
If mode is 'python', the output may contain non-JSON-serializable Python objects.
include: A set of fields to include in the output.
exclude: A set of fields to exclude from the output.
context: Additional context to pass to the serializer.
by_alias: Whether to use the field's alias in the dictionary key if defined.
exclude_unset: Whether to exclude fields that are unset or None from the output.
exclude_defaults: Whether to exclude fields that are set to their default value from the output.
exclude_none: Whether to exclude fields that have a value of `None` from the output.
round_trip: Whether to enable serialization and deserialization round-trip support.
warnings: Whether to log warnings when invalid fields are encountered.
exclude_unset: Whether to exclude fields that have not been explicitly set.
exclude_defaults: Whether to exclude fields that are set to their default value.
exclude_none: Whether to exclude fields that have a value of `None`.
exclude_computed_fields: Whether to exclude computed fields.
While this can be useful for round-tripping, it is usually recommended to use the dedicated
`round_trip` parameter instead.
round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
fallback: A function to call when an unknown value is encountered. If not provided,
a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.

Returns:
A dictionary representation of the model.
Expand All @@ -299,6 +309,8 @@ def model_dump(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
if exclude_computed_fields != False:
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
dumped = super().dict( # pyright: ignore[reportDeprecated]
include=include,
exclude=exclude,
Expand All @@ -315,15 +327,17 @@ def model_dump_json(
self,
*,
indent: int | None = None,
ensure_ascii: bool = False,
include: IncEx | None = None,
exclude: IncEx | None = None,
context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
context: dict[str, Any] | None = None,
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
) -> str:
Expand Down Expand Up @@ -355,6 +369,10 @@ def model_dump_json(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
if ensure_ascii != False:
raise ValueError("ensure_ascii is only supported in Pydantic v2")
if exclude_computed_fields != False:
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
return super().json( # type: ignore[reportDeprecated]
indent=indent,
include=include,
Expand Down Expand Up @@ -574,6 +592,9 @@ class CachedDiscriminatorType(Protocol):
__discriminator__: DiscriminatorDetails


DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()


class DiscriminatorDetails:
field_name: str
"""The name of the discriminator field in the variant class, e.g.
Expand Down Expand Up @@ -616,8 +637,9 @@ def __init__(


def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
if isinstance(union, CachedDiscriminatorType):
return union.__discriminator__
cached = DISCRIMINATOR_CACHE.get(union)
if cached is not None:
return cached

discriminator_field_name: str | None = None

Expand Down Expand Up @@ -670,7 +692,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
discriminator_field=discriminator_field_name,
discriminator_alias=discriminator_alias,
)
cast(CachedDiscriminatorType, union).__discriminator__ = details
DISCRIMINATOR_CACHE.setdefault(union, details)
return details


Expand Down
Loading
Loading