Skip to content

Commit 77a58ca

Browse files
committed
Don't use the "const" optimization when rendering OpenAPI 3.0. Test added.
1 parent 055d707 commit 77a58ca

File tree

2 files changed

+91
-6
lines changed

2 files changed

+91
-6
lines changed

openapi_pydantic/v3/v3_0_3/util.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,53 @@ class GenerateOpenAPI30Schema:
6262
...
6363

6464
elif PYDANTIC_V2:
65+
from enum import Enum
66+
6567
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
6668
from pydantic_core import core_schema
6769

6870
class GenerateOpenAPI30Schema(GenerateJsonSchema):
69-
"""Modify the schema generation for OpenAPI 3.0.
70-
71-
In OpenAPI 3.0, types can not be None, but a special "nullable"
72-
field is available.
73-
"""
71+
"""Modify the schema generation for OpenAPI 3.0."""
7472

7573
def nullable_schema(
7674
self,
7775
schema: core_schema.NullableSchema,
7876
) -> JsonSchemaValue:
77+
"""Generates a JSON schema that matches a schema that allows null values.
78+
79+
In OpenAPI 3.0, types can not be None, but a special "nullable" field is
80+
available.
81+
"""
7982
inner_json_schema = self.generate_inner(schema["schema"])
8083
inner_json_schema["nullable"] = True
8184
return inner_json_schema
8285

86+
def literal_schema(self, schema: core_schema.LiteralSchema) -> JsonSchemaValue:
87+
"""Generates a JSON schema that matches a literal value.
88+
89+
In OpenAPI 3.0, the "const" keyword is not supported, so this
90+
version of this method skips that optimization.
91+
"""
92+
expected = [
93+
v.value if isinstance(v, Enum) else v for v in schema["expected"]
94+
]
95+
96+
types = {type(e) for e in expected}
97+
if types == {str}:
98+
return {"enum": expected, "type": "string"}
99+
elif types == {int}:
100+
return {"enum": expected, "type": "integer"}
101+
elif types == {float}:
102+
return {"enum": expected, "type": "number"}
103+
elif types == {bool}:
104+
return {"enum": expected, "type": "boolean"}
105+
elif types == {list}:
106+
return {"enum": expected, "type": "array"}
107+
# there is not None case because if it's mixed it hits the final `else`
108+
# if it's a single Literal[None] then it becomes a `const` schema above
109+
else:
110+
return {"enum": expected}
111+
83112
else:
84113

85114
class GenerateOpenAPI30Schema:

tests/v3_0_3/test_util.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Callable
2+
from typing import Callable, Literal
33

44
from pydantic import BaseModel, Field
55

@@ -179,6 +179,42 @@ def construct_base_open_api_3() -> OpenAPI:
179179
)
180180

181181

182+
def construct_base_open_api_3_plus() -> OpenAPI:
183+
return OpenAPI(
184+
info=Info(
185+
title="My own API",
186+
version="v0.0.1",
187+
),
188+
paths={
189+
"/ping": PathItem(
190+
post=Operation(
191+
requestBody=RequestBody(
192+
content={
193+
"application/json": MediaType(
194+
media_type_schema=PydanticSchema(
195+
schema_class=PingPlusRequest
196+
)
197+
)
198+
}
199+
),
200+
responses={
201+
"200": Response(
202+
description="pong",
203+
content={
204+
"application/json": MediaType(
205+
media_type_schema=PydanticSchema(
206+
schema_class=PongResponse
207+
)
208+
)
209+
},
210+
)
211+
},
212+
)
213+
)
214+
},
215+
)
216+
217+
182218
class PingRequest(BaseModel):
183219
"""Ping Request"""
184220

@@ -198,3 +234,23 @@ class PongResponse(BaseModel):
198234

199235
resp_foo: str = Field(alias="pong_foo", description="foo value of the response")
200236
resp_bar: str = Field(alias="pong_bar", description="bar value of the response")
237+
238+
239+
class PingPlusRequest(BaseModel):
240+
"""Ping Request with extra"""
241+
242+
req_foo: str
243+
req_bar: str
244+
req_single_choice: Literal["one"]
245+
246+
247+
def test_enum_with_single_choice() -> None:
248+
api_obj = construct_open_api_with_schema_class(construct_base_open_api_3_plus())
249+
model_dump = getattr(api_obj, "model_dump" if PYDANTIC_V2 else "dict")
250+
api = model_dump(by_alias=True, exclude_none=True)
251+
schema = api["components"]["schemas"]["PingPlusRequest"]
252+
prop = schema["properties"]["req_single_choice"]
253+
# OpenAPI 3.0 does not support "const", so make sure the enum is not
254+
# rendered that way.
255+
assert not prop.get("const")
256+
assert prop["enum"] == ["one"]

0 commit comments

Comments
 (0)