Skip to content

Commit abf72c1

Browse files
committed
Support all Pydantic dump options
This adds a `QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS` configuration variable to pass additional keyword arguments to the model_dump method including the now removed `by_alias` argument.
1 parent cd6c97b commit abf72c1

File tree

7 files changed

+56
-23
lines changed

7 files changed

+56
-23
lines changed

docs/how_to_guides/configuration.rst

+10-7
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,21 @@ The following configuration options are used by Quart-Schema. They
55
should be set as part of the standard `Quart configuration
66
<https://pgjones.gitlab.io/quart/how_to_guides/configuration.html>`_.
77

8-
================================== =====
8+
================================== ===================
99
Configuration key type
10-
---------------------------------- -----
10+
---------------------------------- -------------------
1111
QUART_SCHEMA_CONVERSION_PREFERENCE str
1212
QUART_SCHEMA_SWAGGER_JS_URL str
1313
QUART_SCHEMA_SWAGGER_CSS_URL str
1414
QUART_SCHEMA_REDOC_JS_URL str
15-
QUART_SCHEMA_BY_ALIAS bool
15+
QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS PydanticDumpOptions
1616
QUART_SCHEMA_CONVERT_CASING bool
17-
================================== =====
17+
================================== ===================
1818

1919
which allow the js and css for the documentation UI to be changed and
20-
configured and specifies that responses that are Pydantic models
21-
should be converted to JSON by the field alias names (if
22-
``QUART_SCHEMA_BY_ALIAS`` is ``True``).
20+
configured.
21+
22+
The Pydantic Dump Options should be a dictionary ``dict[str, Any]``
23+
and will be passed to Pydantic's model_dump method as keyword
24+
arguments. The options are typed via
25+
:class:`~quart_schema.PydanticDumpOptions`.

src/quart_schema/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
ServerVariable,
2121
Tag,
2222
)
23-
from .typing import ResponseReturnValue
23+
from .typing import PydanticDumpOptions, ResponseReturnValue
2424
from .validation import (
2525
DataSource,
2626
RequestSchemaValidationError,
@@ -50,6 +50,7 @@
5050
"OAuth2SecurityScheme",
5151
"OpenIdSecurityScheme",
5252
"operation_id",
53+
"PydanticDumpOptions",
5354
"QuartSchema",
5455
"RequestSchemaValidationError",
5556
"ResponseReturnValue",

src/quart_schema/conversion.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from werkzeug.datastructures import Headers
1111
from werkzeug.exceptions import HTTPException
1212

13-
from .typing import Model, ResponseReturnValue, ResponseValue
13+
from .typing import Model, PydanticDumpOptions, ResponseReturnValue, ResponseValue
1414

1515
try:
1616
from pydantic import (
@@ -100,14 +100,14 @@ def convert_response_return_value(
100100
value = model_dump(
101101
value,
102102
camelize=current_app.config["QUART_SCHEMA_CONVERT_CASING"],
103-
by_alias=current_app.config["QUART_SCHEMA_BY_ALIAS"],
104103
preference=current_app.config["QUART_SCHEMA_CONVERSION_PREFERENCE"],
104+
pydantic_kwargs=current_app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"],
105105
)
106106
headers = model_dump(
107107
headers, # type: ignore
108108
kebabize=True,
109-
by_alias=current_app.config["QUART_SCHEMA_BY_ALIAS"],
110109
preference=current_app.config["QUART_SCHEMA_CONVERSION_PREFERENCE"],
110+
pydantic_kwargs=current_app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"],
111111
)
112112

113113
new_result: ResponseReturnValue
@@ -128,23 +128,23 @@ def convert_response_return_value(
128128
def model_dump(
129129
raw: ResponseValue,
130130
*,
131-
by_alias: bool,
132131
camelize: bool = False,
133132
kebabize: bool = False,
134133
preference: Optional[str] = None,
134+
pydantic_kwargs: Optional[PydanticDumpOptions] = None,
135135
) -> dict | list:
136136
if is_pydantic_dataclass(type(raw)):
137-
value = RootModel[type(raw)](raw).model_dump() # type: ignore
137+
value = RootModel[type(raw)](raw).model_dump(**(pydantic_kwargs or {})) # type: ignore
138138
elif isinstance(raw, BaseModel):
139-
value = raw.model_dump(by_alias=by_alias)
139+
value = raw.model_dump(**(pydantic_kwargs or {}))
140140
elif isinstance(raw, Struct) or is_attrs(raw): # type: ignore
141141
value = to_builtins(raw)
142142
elif (
143143
(isinstance(raw, (list, dict)) or is_dataclass(raw))
144144
and PYDANTIC_INSTALLED
145145
and preference != "msgspec"
146146
):
147-
value = TypeAdapter(type(raw)).dump_python(raw)
147+
value = TypeAdapter(type(raw)).dump_python(raw, **(pydantic_kwargs or {}))
148148
elif (
149149
(isinstance(raw, (list, dict)) or is_dataclass(raw))
150150
and MSGSPEC_INSTALLED

src/quart_schema/extension.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def init_app(self, app: Quart) -> None:
299299
"QUART_SCHEMA_SCALAR_JS_URL",
300300
"https://cdn.jsdelivr.net/npm/@scalar/api-reference",
301301
)
302-
app.config.setdefault("QUART_SCHEMA_BY_ALIAS", False)
302+
app.config.setdefault("QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS", {})
303303
app.config.setdefault("QUART_SCHEMA_CONVERT_CASING", self.convert_casing)
304304
app.config.setdefault("QUART_SCHEMA_CONVERSION_PREFERENCE", self.conversion_preference)
305305
app.json = create_json_provider(app)

src/quart_schema/mixins.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ async def send_as(self: WebsocketProtocol, value: Any, model_class: Type[Model])
3939
data = model_dump(
4040
value,
4141
camelize=current_app.config["QUART_SCHEMA_CONVERT_CASING"],
42-
by_alias=current_app.config["QUART_SCHEMA_BY_ALIAS"],
4342
preference=current_app.config["QUART_SCHEMA_CONVERSION_PREFERENCE"],
43+
pydantic_kwargs=current_app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"],
4444
)
4545
await self.send_json(data) # type: ignore
4646

@@ -67,23 +67,23 @@ async def _make_request(
6767
json = model_dump(
6868
json,
6969
camelize=self.app.config["QUART_SCHEMA_CONVERT_CASING"],
70-
by_alias=self.app.config["QUART_SCHEMA_BY_ALIAS"],
7170
preference=self.app.config["QUART_SCHEMA_CONVERSION_PREFERENCE"],
71+
pydantic_kwargs=self.app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"],
7272
)
7373

7474
if form is not None:
7575
form = model_dump( # type: ignore
7676
form,
7777
camelize=self.app.config["QUART_SCHEMA_CONVERT_CASING"],
78-
by_alias=self.app.config["QUART_SCHEMA_BY_ALIAS"],
7978
preference=self.app.config["QUART_SCHEMA_CONVERSION_PREFERENCE"],
79+
pydantic_kwargs=self.app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"],
8080
)
8181
if query_string is not None:
8282
query_string = model_dump( # type: ignore
8383
query_string,
8484
camelize=self.app.config["QUART_SCHEMA_CONVERT_CASING"],
85-
by_alias=self.app.config["QUART_SCHEMA_BY_ALIAS"],
8685
preference=self.app.config["QUART_SCHEMA_CONVERSION_PREFERENCE"],
86+
pydantic_kwargs=self.app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"],
8787
)
8888

8989
return await super()._make_request( # type: ignore

src/quart_schema/typing.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
from __future__ import annotations
22

3-
from typing import Any, AnyStr, Callable, Dict, List, Optional, Tuple, Type, TYPE_CHECKING, Union
3+
from typing import (
4+
Any,
5+
AnyStr,
6+
Callable,
7+
Dict,
8+
List,
9+
Literal,
10+
Optional,
11+
Tuple,
12+
Type,
13+
TYPE_CHECKING,
14+
TypedDict,
15+
Union,
16+
)
417

518
from quart import Quart
619
from quart.datastructures import FileStorage
@@ -18,6 +31,12 @@
1831
except ImportError:
1932
from typing_extensions import Protocol # type: ignore
2033

34+
try:
35+
from typing import NotRequired
36+
except ImportError:
37+
from typing_extensions import NotRequired
38+
39+
2140
if TYPE_CHECKING:
2241
from attrs import AttrsInstance
2342
from msgspec import Struct
@@ -69,3 +88,13 @@ async def _make_request(
6988
http_version: str,
7089
scope_base: Optional[dict],
7190
) -> Response: ...
91+
92+
93+
class PydanticDumpOptions(TypedDict):
94+
by_alias: NotRequired[bool]
95+
exclude_defaults: NotRequired[bool]
96+
exclude_none: NotRequired[bool]
97+
exclude_unset: NotRequired[bool]
98+
round_trip: NotRequired[bool]
99+
serialize_as_any: NotRequired[bool]
100+
warnings: NotRequired[bool | Literal["none", "warn", "error"]]

tests/test_conversion.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ValidationError(Exception):
1919
def test_model_dump(
2020
type_: Type[Union[ADetails, DCDetails, MDetails, PyDetails, PyDCDetails]]
2121
) -> None:
22-
assert model_dump(type_(name="bob", age=2), by_alias=False) == { # type: ignore
22+
assert model_dump(type_(name="bob", age=2)) == { # type: ignore
2323
"name": "bob",
2424
"age": 2,
2525
}
@@ -41,7 +41,7 @@ def test_model_dump_list(
4141
preference: str,
4242
) -> None:
4343
assert model_dump(
44-
[type_(name="bob", age=2), type_(name="jim", age=3)], by_alias=False, preference=preference
44+
[type_(name="bob", age=2), type_(name="jim", age=3)], preference=preference
4545
) == [{"name": "bob", "age": 2}, {"name": "jim", "age": 3}]
4646

4747

0 commit comments

Comments
 (0)