From 41b65ded09e9f25c5986179909e1d46df67bf805 Mon Sep 17 00:00:00 2001 From: SimonTaurus Date: Thu, 24 Sep 2026 06:38:03 +0200 Subject: [PATCH] fix(v1): route json() dumps kwargs to json.dumps, not dict() - indent, sort_keys and separators reached BaseModel.dict() and raised TypeError; pydantic v1 names the dict() arguments and collects the rest - add the v1 parity test that covers the split --- src/oold/model/v1/_descriptor.py | 11 ++++++++++- tests/test_compat_parity_v1.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/oold/model/v1/_descriptor.py b/src/oold/model/v1/_descriptor.py index ea8d52f..ad19092 100644 --- a/src/oold/model/v1/_descriptor.py +++ b/src/oold/model/v1/_descriptor.py @@ -17,6 +17,7 @@ from __future__ import annotations +import inspect import json from typing import Any, TypeVar, overload @@ -36,6 +37,9 @@ _MANY_SHAPES = {SHAPE_LIST, SHAPE_SET, SHAPE_TUPLE} +_DICT_KWARGS = frozenset(inspect.signature(BaseModel.dict).parameters) - {"self"} +"""The arguments ``BaseModel.dict`` accepts, which ``json`` has to route to it.""" + _M = TypeVar("_M") _TYPE_REGISTRY: dict[str, type] = {} @@ -345,7 +349,12 @@ def json(self, **kwargs: Any) -> str: # rejects them. encoder = kwargs.pop("encoder", None) or self.__json_encoder__ kwargs.pop("models_as_dict", None) - return json.dumps(self.dict(**kwargs), default=encoder) + # pydantic v1 names the dict() arguments explicitly and collects the + # rest into **dumps_kwargs. Anything not selecting data - indent, + # sort_keys, separators - belongs to json.dumps, and dict() raises + # TypeError on it. + dumps_kwargs = {key: kwargs.pop(key) for key in list(kwargs) if key not in _DICT_KWARGS} + return json.dumps(self.dict(**kwargs), default=encoder, **dumps_kwargs) def to_json(self, exclude_defaults: bool = False) -> dict[str, Any]: return json.loads(self.json(exclude_none=True, exclude_defaults=exclude_defaults)) diff --git a/tests/test_compat_parity_v1.py b/tests/test_compat_parity_v1.py index d7b7109..98b4fc2 100644 --- a/tests/test_compat_parity_v1.py +++ b/tests/test_compat_parity_v1.py @@ -97,6 +97,20 @@ def probe(tag, T, M): assert shipped == auto +def test_json_passes_dumps_kwargs_to_json_dumps(): + """pydantic v1 splits json()'s arguments: the dict() ones select the data, + the rest go to json.dumps. Routing all of them to dict() raises TypeError on + indent, sort_keys and separators, which are valid pydantic v1 calls.""" + + def probe(tag, T, M): + m = M(id="ex:m", title="x", links=[f"ex:{tag}1", f"ex:{tag}2"], one=f"ex:{tag}1") + return m.json(exclude_none=True, indent=2, sort_keys=True, separators=(",", ": ")) + + shipped, auto = collect(probe) + assert "\n " in auto, f"indent never reached json.dumps: {auto!r}" + assert shipped == auto + + def test_raw_dict_lists_every_field(): def probe(tag, T, M): m = M(id="ex:m", title="x", one=f"ex:{tag}1")