Skip to content

Commit a43534c

Browse files
release: 2.3.3-rc1 (#250)
* chore(internal): detect missing future annotations with ruff * chore: bump `httpx-aiohttp` version to 0.1.9 * fix(client): close streams without requiring full consumption * chore(internal/tests): avoid race condition with implicit client cleanup * chore(internal): grammar fix (it's -> its) * chore(package): drop Python 3.8 support * fix: compat with Python 3.14 * codegen metadata * fix(compat): update signatures of `model_dump` and `model_dump_json` for Pydantic v1 * chore(internal): codegen related update * fix: ensure streams are always closed * chore(deps): mypy 1.18.1 has a regression, pin to 1.17 * docs(api): updates to API spec * release: 2.3.3-rc1 --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 6e949c8 commit a43534c

22 files changed

+441
-307
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.3.2"
2+
".": "2.3.3-rc1"
33
}

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 33
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-4ec783072dd7f57c6e021a746df7650fb8d7a164d8ec25c7d5cab06c33bc114f.yml
3-
openapi_spec_hash: ceab065d515f3681b0c33137da308968
4-
config_hash: 089fd5502b9cf91247887b19117f1ca2
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-ea6ec4b34f6b7fdecc564f59b2e31482eee05830bf8dc1f389461b158de1548e.yml
3+
openapi_spec_hash: ea89c1faed473908be2740efe6da255f
4+
config_hash: 886645f89dc98f04b8931eaf02854e5f

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
# Changelog
22

3+
## 2.3.3-rc1 (2025-12-01)
4+
5+
Full Changelog: [v2.3.2...v2.3.3-rc1](https://github.com/writer/writer-python/compare/v2.3.2...v2.3.3-rc1)
6+
7+
### Bug Fixes
8+
9+
* **client:** close streams without requiring full consumption ([31e3903](https://github.com/writer/writer-python/commit/31e39034cab026c34c9509757a27d9e2221c0c5b))
10+
* compat with Python 3.14 ([56db271](https://github.com/writer/writer-python/commit/56db2716054e1ba6a23071e172584e7c2433ba87))
11+
* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([1fb3322](https://github.com/writer/writer-python/commit/1fb332284ab2c7ff87afeb686176df1efcf262db))
12+
* ensure streams are always closed ([23c7971](https://github.com/writer/writer-python/commit/23c7971d69301956cef01d0041120a848818fa5a))
13+
14+
15+
### Chores
16+
17+
* bump `httpx-aiohttp` version to 0.1.9 ([f2ef07d](https://github.com/writer/writer-python/commit/f2ef07dbe6ffd744bf58a6c7b5f3dac8b73a8805))
18+
* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([74b4799](https://github.com/writer/writer-python/commit/74b479957daea7272bfd0a7533125b0bd42c17dd))
19+
* **internal/tests:** avoid race condition with implicit client cleanup ([828ac4d](https://github.com/writer/writer-python/commit/828ac4d2a57d4f623d4fe2aef25390c5f0051b96))
20+
* **internal:** codegen related update ([3b5b4a6](https://github.com/writer/writer-python/commit/3b5b4a69314e7c3853018233796b05a4035710fb))
21+
* **internal:** detect missing future annotations with ruff ([9df4451](https://github.com/writer/writer-python/commit/9df44512304949e6193e7ff33390342e26d065c6))
22+
* **internal:** grammar fix (it's -&gt; its) ([e8b1113](https://github.com/writer/writer-python/commit/e8b11131528095f8acf847d126fda21cec0b66c6))
23+
* **package:** drop Python 3.8 support ([9b204ce](https://github.com/writer/writer-python/commit/9b204ced5e50fa180e24b3d05ec271b8bbd7baff))
24+
25+
26+
### Documentation
27+
28+
* **api:** updates to API spec ([04fe076](https://github.com/writer/writer-python/commit/04fe0769dcba588b421d5d6fe3fd5b7cf10a726d))
29+
330
## 2.3.2 (2025-10-03)
431

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

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<!-- prettier-ignore -->
44
[![PyPI version](https://img.shields.io/pypi/v/writer-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/writer-sdk/)
55

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

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

2020
```sh
2121
# install from PyPI
22-
pip install writer-sdk
22+
pip install --pre writer-sdk
2323
```
2424

2525
## Prequisites
@@ -116,7 +116,7 @@ You can enable this by installing `aiohttp`:
116116

117117
```sh
118118
# install from PyPI
119-
pip install writer-sdk[aiohttp]
119+
pip install --pre writer-sdk[aiohttp]
120120
```
121121

122122
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:

pyproject.toml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "writer-sdk"
3-
version = "2.3.2"
3+
version = "2.3.3-rc1"
44
description = "The official Python library for the writer API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"
@@ -17,16 +17,16 @@ dependencies = [
1717
"cached-property; python_version < '3.8'",
1818
"jiter>=0.4.0, <1",
1919
]
20-
requires-python = ">= 3.8"
20+
requires-python = ">= 3.9"
2121
classifiers = [
2222
"Typing :: Typed",
2323
"Intended Audience :: Developers",
24-
"Programming Language :: Python :: 3.8",
2524
"Programming Language :: Python :: 3.9",
2625
"Programming Language :: Python :: 3.10",
2726
"Programming Language :: Python :: 3.11",
2827
"Programming Language :: Python :: 3.12",
2928
"Programming Language :: Python :: 3.13",
29+
"Programming Language :: Python :: 3.14",
3030
"Operating System :: OS Independent",
3131
"Operating System :: POSIX",
3232
"Operating System :: MacOS",
@@ -41,14 +41,14 @@ Homepage = "https://github.com/writer/writer-python"
4141
Repository = "https://github.com/writer/writer-python"
4242

4343
[project.optional-dependencies]
44-
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
44+
aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
4545

4646
[tool.rye]
4747
managed = true
4848
# version pins are in requirements-dev.lock
4949
dev-dependencies = [
5050
"pyright==1.1.399",
51-
"mypy",
51+
"mypy==1.17",
5252
"respx",
5353
"pytest",
5454
"pytest-asyncio",
@@ -145,7 +145,7 @@ filterwarnings = [
145145
# there are a couple of flags that are still disabled by
146146
# default in strict mode as they are experimental and niche.
147147
typeCheckingMode = "strict"
148-
pythonVersion = "3.8"
148+
pythonVersion = "3.9"
149149

150150
exclude = [
151151
"_dev",
@@ -228,6 +228,8 @@ select = [
228228
"B",
229229
# remove unused imports
230230
"F401",
231+
# check for missing future annotations
232+
"FA102",
231233
# bare except statements
232234
"E722",
233235
# unused arguments
@@ -250,6 +252,8 @@ unfixable = [
250252
"T203",
251253
]
252254

255+
extend-safe-fixes = ["FA102"]
256+
253257
[tool.ruff.lint.flake8-tidy-imports.banned-api]
254258
"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"
255259

requirements-dev.lock

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ httpx==0.28.1
6060
# via httpx-aiohttp
6161
# via respx
6262
# via writer-sdk
63-
httpx-aiohttp==0.1.8
63+
httpx-aiohttp==0.1.9
6464
# via writer-sdk
6565
idna==3.4
6666
# via anyio
@@ -79,7 +79,7 @@ mdurl==0.1.2
7979
multidict==6.4.4
8080
# via aiohttp
8181
# via yarl
82-
mypy==1.14.1
82+
mypy==1.17.0
8383
mypy-extensions==1.0.0
8484
# via mypy
8585
nest-asyncio==1.6.0
@@ -89,6 +89,8 @@ nox==2023.4.22
8989
packaging==23.2
9090
# via nox
9191
# via pytest
92+
pathspec==0.12.1
93+
# via mypy
9294
platformdirs==3.11.0
9395
# via virtualenv
9496
pluggy==1.5.0

requirements.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ httpcore==1.0.9
4343
httpx==0.28.1
4444
# via httpx-aiohttp
4545
# via writer-sdk
46-
httpx-aiohttp==0.1.8
46+
httpx-aiohttp==0.1.9
4747
# via writer-sdk
4848
idna==3.4
4949
# via anyio
@@ -57,21 +57,21 @@ multidict==6.4.4
5757
propcache==0.3.1
5858
# via aiohttp
5959
# via yarl
60-
pydantic==2.11.9
60+
pydantic==2.12.5
6161
# via writer-sdk
62-
pydantic-core==2.33.2
62+
pydantic-core==2.41.5
6363
# via pydantic
6464
sniffio==1.3.0
6565
# via anyio
6666
# via writer-sdk
67-
typing-extensions==4.12.2
67+
typing-extensions==4.15.0
6868
# via anyio
6969
# via multidict
7070
# via pydantic
7171
# via pydantic-core
7272
# via typing-inspection
7373
# via writer-sdk
74-
typing-inspection==0.4.1
74+
typing-inspection==0.4.2
7575
# via pydantic
7676
yarl==1.20.0
7777
# via aiohttp

src/writerai/_models.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import inspect
5+
import weakref
56
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
67
from datetime import date, datetime
78
from typing_extensions import (
@@ -257,32 +258,41 @@ def model_dump(
257258
mode: Literal["json", "python"] | str = "python",
258259
include: IncEx | None = None,
259260
exclude: IncEx | None = None,
261+
context: Any | None = None,
260262
by_alias: bool | None = None,
261263
exclude_unset: bool = False,
262264
exclude_defaults: bool = False,
263265
exclude_none: bool = False,
266+
exclude_computed_fields: bool = False,
264267
round_trip: bool = False,
265268
warnings: bool | Literal["none", "warn", "error"] = True,
266-
context: dict[str, Any] | None = None,
267-
serialize_as_any: bool = False,
268269
fallback: Callable[[Any], Any] | None = None,
270+
serialize_as_any: bool = False,
269271
) -> dict[str, Any]:
270272
"""Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump
271273
272274
Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
273275
274276
Args:
275277
mode: The mode in which `to_python` should run.
276-
If mode is 'json', the dictionary will only contain JSON serializable types.
277-
If mode is 'python', the dictionary may contain any Python objects.
278-
include: A list of fields to include in the output.
279-
exclude: A list of fields to exclude from the output.
278+
If mode is 'json', the output will only contain JSON serializable types.
279+
If mode is 'python', the output may contain non-JSON-serializable Python objects.
280+
include: A set of fields to include in the output.
281+
exclude: A set of fields to exclude from the output.
282+
context: Additional context to pass to the serializer.
280283
by_alias: Whether to use the field's alias in the dictionary key if defined.
281-
exclude_unset: Whether to exclude fields that are unset or None from the output.
282-
exclude_defaults: Whether to exclude fields that are set to their default value from the output.
283-
exclude_none: Whether to exclude fields that have a value of `None` from the output.
284-
round_trip: Whether to enable serialization and deserialization round-trip support.
285-
warnings: Whether to log warnings when invalid fields are encountered.
284+
exclude_unset: Whether to exclude fields that have not been explicitly set.
285+
exclude_defaults: Whether to exclude fields that are set to their default value.
286+
exclude_none: Whether to exclude fields that have a value of `None`.
287+
exclude_computed_fields: Whether to exclude computed fields.
288+
While this can be useful for round-tripping, it is usually recommended to use the dedicated
289+
`round_trip` parameter instead.
290+
round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
291+
warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
292+
"error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
293+
fallback: A function to call when an unknown value is encountered. If not provided,
294+
a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
295+
serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
286296
287297
Returns:
288298
A dictionary representation of the model.
@@ -299,6 +309,8 @@ def model_dump(
299309
raise ValueError("serialize_as_any is only supported in Pydantic v2")
300310
if fallback is not None:
301311
raise ValueError("fallback is only supported in Pydantic v2")
312+
if exclude_computed_fields != False:
313+
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
302314
dumped = super().dict( # pyright: ignore[reportDeprecated]
303315
include=include,
304316
exclude=exclude,
@@ -315,15 +327,17 @@ def model_dump_json(
315327
self,
316328
*,
317329
indent: int | None = None,
330+
ensure_ascii: bool = False,
318331
include: IncEx | None = None,
319332
exclude: IncEx | None = None,
333+
context: Any | None = None,
320334
by_alias: bool | None = None,
321335
exclude_unset: bool = False,
322336
exclude_defaults: bool = False,
323337
exclude_none: bool = False,
338+
exclude_computed_fields: bool = False,
324339
round_trip: bool = False,
325340
warnings: bool | Literal["none", "warn", "error"] = True,
326-
context: dict[str, Any] | None = None,
327341
fallback: Callable[[Any], Any] | None = None,
328342
serialize_as_any: bool = False,
329343
) -> str:
@@ -355,6 +369,10 @@ def model_dump_json(
355369
raise ValueError("serialize_as_any is only supported in Pydantic v2")
356370
if fallback is not None:
357371
raise ValueError("fallback is only supported in Pydantic v2")
372+
if ensure_ascii != False:
373+
raise ValueError("ensure_ascii is only supported in Pydantic v2")
374+
if exclude_computed_fields != False:
375+
raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
358376
return super().json( # type: ignore[reportDeprecated]
359377
indent=indent,
360378
include=include,
@@ -574,6 +592,9 @@ class CachedDiscriminatorType(Protocol):
574592
__discriminator__: DiscriminatorDetails
575593

576594

595+
DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()
596+
597+
577598
class DiscriminatorDetails:
578599
field_name: str
579600
"""The name of the discriminator field in the variant class, e.g.
@@ -616,8 +637,9 @@ def __init__(
616637

617638

618639
def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
619-
if isinstance(union, CachedDiscriminatorType):
620-
return union.__discriminator__
640+
cached = DISCRIMINATOR_CACHE.get(union)
641+
if cached is not None:
642+
return cached
621643

622644
discriminator_field_name: str | None = None
623645

@@ -670,7 +692,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
670692
discriminator_field=discriminator_field_name,
671693
discriminator_alias=discriminator_alias,
672694
)
673-
cast(CachedDiscriminatorType, union).__discriminator__ = details
695+
DISCRIMINATOR_CACHE.setdefault(union, details)
674696
return details
675697

676698

0 commit comments

Comments
 (0)