Skip to content

Commit 14be31d

Browse files
committed
Remove null as a data type in OpenAPI 3.0; replace it with nullable
Also add some docstrings to compat.py for clarity.
1 parent 96787dd commit 14be31d

File tree

6 files changed

+93
-48
lines changed

6 files changed

+93
-48
lines changed

openapi_pydantic/compat.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44

55
from pydantic.version import VERSION as PYDANTIC_VERSION
66

7+
__all__ = [
8+
"PYDANTIC_V2",
9+
"ConfigDict",
10+
"JsonSchemaMode",
11+
"models_json_schema",
12+
"RootModel",
13+
"Extra",
14+
"v1_schema",
15+
"DEFS_KEY",
16+
"min_length_arg",
17+
]
18+
719
PYDANTIC_MAJOR_VERSION = int(PYDANTIC_VERSION.split(".", 1)[0])
820
PYDANTIC_V2 = PYDANTIC_MAJOR_VERSION >= 2
921

@@ -21,15 +33,18 @@ def ConfigDict(
2133
json_schema_extra: dict[str, Any] | None = None,
2234
populate_by_name: bool = True,
2335
) -> PydanticConfigDict:
36+
"""Stub for pydantic.ConfigDict in Pydantic 2"""
2437
...
2538

2639
class Extra(Enum):
40+
"""Stub for pydantic.Extra in Pydantic 1"""
41+
2742
allow = "allow"
2843
ignore = "ignore"
2944
forbid = "forbid"
3045

3146
class RootModel(BaseModel):
32-
...
47+
"""Stub for pydantic.RootModel in Pydantic 2"""
3348

3449
JsonSchemaMode = Literal["validation", "serialization"]
3550

@@ -38,7 +53,9 @@ def models_json_schema(
3853
*,
3954
by_alias: bool = True,
4055
ref_template: str = "#/$defs/{model}",
56+
schema_generator: type | None = None,
4157
) -> tuple[dict, dict[str, Any]]:
58+
"""Stub for pydantic.json_schema.models_json_schema in Pydantic 2"""
4259
...
4360

4461
def v1_schema(
@@ -47,6 +64,7 @@ def v1_schema(
4764
by_alias: bool = True,
4865
ref_prefix: str = "#/$defs",
4966
) -> dict[str, Any]:
67+
"""Stub for pydantic.schema.schema in Pydantic 1"""
5068
...
5169

5270
DEFS_KEY = "$defs"
@@ -55,10 +73,11 @@ class MinLengthArg(TypedDict):
5573
pass
5674

5775
def min_length_arg(min_length: int) -> MinLengthArg:
76+
"""Generate a min_length or min_items parameter for Field(...)"""
5877
...
5978

6079
elif PYDANTIC_V2:
61-
from typing import Literal, TypedDict
80+
from typing import TypedDict
6281

6382
from pydantic import ConfigDict, RootModel
6483
from pydantic.json_schema import JsonSchemaMode, models_json_schema
@@ -72,7 +91,7 @@ class MinLengthArg(TypedDict):
7291
def min_length_arg(min_length: int) -> MinLengthArg:
7392
return {"min_length": min_length}
7493

75-
# Create V1 stubs
94+
# Create V1 stubs, These should not be used when PYDANTIC_V2 is true.
7695
Extra = None
7796
v1_schema = None
7897

@@ -92,23 +111,8 @@ class MinLengthArg(TypedDict):
92111
def min_length_arg(min_length: int) -> MinLengthArg:
93112
return {"min_items": min_length}
94113

95-
# Create V2 stubs
114+
# Create V2 stubs. These should not be used when PYDANTIC_V2 is false.
96115
ConfigDict = None
97-
Literal = None
98116
models_json_schema = None
99117
JsonSchemaMode = None
100118
RootModel = None
101-
102-
103-
__all__ = [
104-
"PYDANTIC_V2",
105-
"Literal",
106-
"ConfigDict",
107-
"JsonSchemaMode",
108-
"models_json_schema",
109-
"RootModel",
110-
"Extra",
111-
"v1_schema",
112-
"DEFS_KEY",
113-
"min_length_arg",
114-
]

openapi_pydantic/util.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,23 @@ def construct_open_api_with_schema_class(
104104
f'"{existing_key}" already exists in {ref_prefix}. '
105105
f'The value of "{ref_prefix}{existing_key}" will be overwritten.'
106106
)
107-
new_open_api.components.schemas.update(
108-
{
109-
key: schema_validate(schema_dict)
110-
for key, schema_dict in schema_definitions[DEFS_KEY].items()
111-
}
112-
)
107+
new_open_api.components.schemas.update(_validate_schemas(schema_definitions))
113108
else:
114-
for key, schema_dict in schema_definitions[DEFS_KEY].items():
115-
schema_validate(schema_dict)
116-
new_open_api.components.schemas = {
117-
key: schema_validate(schema_dict)
118-
for key, schema_dict in schema_definitions[DEFS_KEY].items()
119-
}
109+
new_open_api.components.schemas = _validate_schemas(schema_definitions)
120110
return new_open_api
121111

122112

113+
def _validate_schemas(schema_definitions: dict[str, Any]) -> dict[str, Schema]:
114+
"""Convert JSON Schema definitions to parsed OpenAPI objects"""
115+
# Note: if an error occurs in schema_validate(), it may indicate that
116+
# the generated JSON schemas are not compatible with the version
117+
# of OpenAPI this module depends on.
118+
return {
119+
key: schema_validate(schema_dict)
120+
for key, schema_dict in schema_definitions[DEFS_KEY].items()
121+
}
122+
123+
123124
def _handle_pydantic_schema(open_api: OpenAPI) -> List[Type[BaseModel]]:
124125
"""
125126
This function traverses the `OpenAPI` object and

openapi_pydantic/v3/v3_0_3/datatype.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33

44
class DataType(str, enum.Enum):
5-
"""Data type of an object."""
5+
"""Data type of an object.
6+
7+
Note: OpenAPI 3.0.x does not support null as a data type.
8+
"""
69

7-
NULL = "null"
810
STRING = "string"
911
NUMBER = "number"
1012
INTEGER = "integer"

openapi_pydantic/v3/v3_0_3/header.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import Field
44

5-
from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra, Literal
5+
from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
66

77
from .parameter import Parameter, ParameterLocation
88

@@ -34,6 +34,8 @@ class Header(Parameter):
3434
)
3535

3636
elif PYDANTIC_V2:
37+
from typing import Literal
38+
3739
LiteralEmptyString = Literal[""]
3840

3941
class Header(Parameter):

openapi_pydantic/v3/v3_0_3/util.py

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Any, Generic, List, Optional, Set, Type, TypeVar, cast
2+
from typing import TYPE_CHECKING, Any, Generic, List, Optional, Set, Type, TypeVar, cast
33

44
from pydantic import BaseModel
55

@@ -44,6 +44,36 @@ def get_mode(
4444
return cast(JsonSchemaMode, mode)
4545

4646

47+
if TYPE_CHECKING:
48+
49+
class GenerateOpenAPI30Schema:
50+
...
51+
52+
elif PYDANTIC_V2:
53+
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
54+
from pydantic_core import core_schema
55+
56+
class GenerateOpenAPI30Schema(GenerateJsonSchema):
57+
"""Modify the schema generation for OpenAPI 3.0.
58+
59+
In OpenAPI 3.0, types can not be None, but a special "nullable"
60+
field is available.
61+
"""
62+
63+
def nullable_schema(
64+
self,
65+
schema: core_schema.NullableSchema,
66+
) -> JsonSchemaValue:
67+
inner_json_schema = self.generate_inner(schema["schema"])
68+
inner_json_schema["nullable"] = True
69+
return inner_json_schema
70+
71+
else:
72+
73+
class GenerateOpenAPI30Schema:
74+
...
75+
76+
4777
def construct_open_api_with_schema_class(
4878
open_api: OpenAPI,
4979
schema_classes: Optional[List[Type[BaseModel]]] = None,
@@ -89,6 +119,7 @@ def construct_open_api_with_schema_class(
89119
[(c, get_mode(c)) for c in schema_classes],
90120
by_alias=by_alias,
91121
ref_template=ref_template,
122+
schema_generator=GenerateOpenAPI30Schema,
92123
)
93124
else:
94125
schema_definitions = v1_schema(
@@ -104,22 +135,25 @@ def construct_open_api_with_schema_class(
104135
f'"{existing_key}" already exists in {ref_prefix}. '
105136
f'The value of "{ref_prefix}{existing_key}" will be overwritten.'
106137
)
107-
new_open_api.components.schemas.update(
108-
{
109-
key: schema_validate(schema_dict)
110-
for key, schema_dict in schema_definitions[DEFS_KEY].items()
111-
}
112-
)
138+
new_open_api.components.schemas.update(_validate_schemas(schema_definitions))
113139
else:
114-
for key, schema_dict in schema_definitions[DEFS_KEY].items():
115-
schema_validate(schema_dict)
116-
new_open_api.components.schemas = {
117-
key: schema_validate(schema_dict)
118-
for key, schema_dict in schema_definitions[DEFS_KEY].items()
119-
}
140+
new_open_api.components.schemas = _validate_schemas(schema_definitions)
120141
return new_open_api
121142

122143

144+
def _validate_schemas(
145+
schema_definitions: dict[str, Any]
146+
) -> dict[str, Reference | Schema]:
147+
"""Convert JSON Schema definitions to parsed OpenAPI objects"""
148+
# Note: if an error occurs in schema_validate(), it may indicate that
149+
# the generated JSON schemas are not compatible with the version
150+
# of OpenAPI this module depends on.
151+
return {
152+
key: schema_validate(schema_dict)
153+
for key, schema_dict in schema_definitions[DEFS_KEY].items()
154+
}
155+
156+
123157
def _handle_pydantic_schema(open_api: OpenAPI) -> List[Type[BaseModel]]:
124158
"""
125159
This function traverses the `OpenAPI` object and

openapi_pydantic/v3/v3_1_0/header.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pydantic import Field
44

5-
from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra, Literal
5+
from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
66

77
from .parameter import Parameter, ParameterLocation
88

@@ -33,6 +33,8 @@ class Header(Parameter):
3333
)
3434

3535
elif PYDANTIC_V2:
36+
from typing import Literal
37+
3638
LiteralEmptyString = Literal[""]
3739

3840
class Header(Parameter):

0 commit comments

Comments
 (0)