Skip to content

Commit b7f3f3c

Browse files
authored
Merge pull request #22 from OpenPaymentNetwork/main
Correct Header objects
2 parents 69bd862 + 87821b3 commit b7f3f3c

14 files changed

+1318
-563
lines changed

openapi_pydantic/util.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ def construct_open_api_with_schema_class(
7171
if scan_for_pydantic_schema_reference:
7272
extracted_schema_classes = _handle_pydantic_schema(new_open_api)
7373
if schema_classes:
74-
schema_classes = list(
75-
{*schema_classes, *_handle_pydantic_schema(new_open_api)}
76-
)
74+
schema_classes = list({*schema_classes, *extracted_schema_classes})
7775
else:
7876
schema_classes = extracted_schema_classes
7977

openapi_pydantic/v3/v3_0_3/header.py

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
from typing import TYPE_CHECKING
2-
3-
from pydantic import Field
4-
51
from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
62

7-
from .parameter import Parameter, ParameterLocation
3+
from .parameter import ParameterBase
84

95
_examples = [
106
{
@@ -14,49 +10,26 @@
1410
]
1511

1612

17-
if TYPE_CHECKING:
18-
19-
class Header(Parameter):
20-
"""
21-
The Header Object follows the structure of the
22-
[Parameter Object](#parameterObject) with the following changes:
23-
24-
1. `name` MUST NOT be specified, it is given in the corresponding
25-
`headers` map.
26-
2. `in` MUST NOT be specified, it is implicitly in `header`.
27-
3. All traits that are affected by the location MUST be applicable
28-
to a location of `header` (for example, [`style`](#parameterStyle)).
29-
"""
30-
31-
name: str = Field(default="")
32-
param_in: ParameterLocation = Field(
33-
default=ParameterLocation.HEADER, alias="in"
34-
)
13+
class Header(ParameterBase):
14+
"""
15+
The Header Object follows the structure of the
16+
[Parameter Object](#parameterObject) with the following changes:
3517
36-
elif PYDANTIC_V2:
37-
from typing import Literal
38-
39-
LiteralEmptyString = Literal[""]
40-
41-
class Header(Parameter):
42-
name: LiteralEmptyString = Field(default="")
43-
param_in: Literal[ParameterLocation.HEADER] = Field(
44-
default=ParameterLocation.HEADER, alias="in"
45-
)
18+
1. `name` MUST NOT be specified, it is given in the corresponding
19+
`headers` map.
20+
2. `in` MUST NOT be specified, it is implicitly in `header`.
21+
3. All traits that are affected by the location MUST be applicable
22+
to a location of `header` (for example, [`style`](#parameterStyle)).
23+
"""
4624

25+
if PYDANTIC_V2:
4726
model_config = ConfigDict(
4827
extra="allow",
4928
populate_by_name=True,
5029
json_schema_extra={"examples": _examples},
5130
)
5231

53-
else:
54-
55-
class Header(Parameter):
56-
name: str = Field(default="", const=True)
57-
param_in: ParameterLocation = Field(
58-
default=ParameterLocation.HEADER, const=True, alias="in"
59-
)
32+
else:
6033

6134
class Config:
6235
extra = Extra.allow

openapi_pydantic/v3/v3_0_3/parameter.py

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -75,35 +75,11 @@ class ParameterLocation(str, enum.Enum):
7575
COOKIE = "cookie"
7676

7777

78-
class Parameter(BaseModel):
79-
"""
80-
Describes a single operation parameter.
81-
82-
A unique parameter is defined by a combination of a [name](#parameterName) and
83-
[location](#parameterIn).
84-
"""
85-
86-
"""Fixed Fields"""
87-
88-
name: str
89-
"""
90-
**REQUIRED**. The name of the parameter.
91-
Parameter names are *case sensitive*.
92-
93-
- If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a
94-
template expression occurring within the [path](#pathsPath) field in the
95-
[Paths Object](#pathsObject). See [Path Templating](#pathTemplating) for further
96-
information.
97-
- If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`,
98-
`"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored.
99-
- For all other cases, the `name` corresponds to the parameter name used by the
100-
[`in`](#parameterIn) property.
78+
class ParameterBase(BaseModel):
10179
"""
80+
Base class for Parameter and Header.
10281
103-
param_in: ParameterLocation = Field(alias="in")
104-
"""
105-
**REQUIRED**. The location of the parameter. Possible values are `"query"`,
106-
`"header"`, `"path"` or `"cookie"`.
82+
(Header is like Parameter, but has no `name` or `in` fields.)
10783
"""
10884

10985
description: Optional[str] = None
@@ -128,23 +104,6 @@ class Parameter(BaseModel):
128104
Default value is `false`.
129105
"""
130106

131-
allowEmptyValue: bool = False
132-
"""
133-
Sets the ability to pass empty-valued parameters.
134-
This is valid only for `query` parameters and allows sending a parameter with an
135-
empty value. Default value is `false`.
136-
If [`style`](#parameterStyle) is used, and if behavior is `n/a` (cannot be
137-
serialized), the value of `allowEmptyValue` SHALL be ignored.
138-
Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later
139-
revision.
140-
"""
141-
142-
"""
143-
The rules for serialization of the parameter are specified in one of two ways.
144-
For simpler scenarios, a [`schema`](#parameterSchema) and [`style`](#parameterStyle)
145-
can describe the structure and syntax of the parameter.
146-
"""
147-
148107
style: Optional[str] = None
149108
"""
150109
Describes how the parameter value will be serialized depending on the type of the
@@ -165,15 +124,6 @@ class Parameter(BaseModel):
165124
For all other styles, the default value is `false`.
166125
"""
167126

168-
allowReserved: bool = False
169-
"""
170-
Determines whether the parameter value SHOULD allow reserved characters,
171-
as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
172-
`:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
173-
This property only applies to parameters with an `in` value of `query`.
174-
The default value is `false`.
175-
"""
176-
177127
param_schema: Optional[Union[Reference, Schema]] = Field(
178128
default=None, alias="schema"
179129
)
@@ -230,3 +180,55 @@ class Config:
230180
extra = Extra.allow
231181
allow_population_by_field_name = True
232182
schema_extra = {"examples": _examples}
183+
184+
185+
class Parameter(ParameterBase):
186+
"""
187+
Describes a single operation parameter.
188+
189+
A unique parameter is defined by a combination of a [name](#parameterName) and
190+
[location](#parameterIn).
191+
"""
192+
193+
"""Fixed Fields"""
194+
195+
name: str
196+
"""
197+
**REQUIRED**. The name of the parameter.
198+
Parameter names are *case sensitive*.
199+
200+
- If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a
201+
template expression occurring within the [path](#pathsPath) field in the
202+
[Paths Object](#pathsObject). See [Path Templating](#pathTemplating) for further
203+
information.
204+
- If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`,
205+
`"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored.
206+
- For all other cases, the `name` corresponds to the parameter name used by the
207+
[`in`](#parameterIn) property.
208+
"""
209+
210+
param_in: ParameterLocation = Field(alias="in")
211+
"""
212+
**REQUIRED**. The location of the parameter. Possible values are `"query"`,
213+
`"header"`, `"path"` or `"cookie"`.
214+
"""
215+
216+
allowEmptyValue: bool = False
217+
"""
218+
Sets the ability to pass empty-valued parameters.
219+
This is valid only for `query` parameters and allows sending a parameter with an
220+
empty value. Default value is `false`.
221+
If [`style`](#parameterStyle) is used, and if behavior is `n/a` (cannot be
222+
serialized), the value of `allowEmptyValue` SHALL be ignored.
223+
Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later
224+
revision.
225+
"""
226+
227+
allowReserved: bool = False
228+
"""
229+
Determines whether the parameter value SHOULD allow reserved characters,
230+
as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
231+
`:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
232+
This property only applies to parameters with an `in` value of `query`.
233+
The default value is `false`.
234+
"""

openapi_pydantic/v3/v3_0_3/util.py

Lines changed: 35 additions & 8 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:
@@ -113,9 +142,7 @@ def construct_open_api_with_schema_class(
113142
if scan_for_pydantic_schema_reference:
114143
extracted_schema_classes = _handle_pydantic_schema(new_open_api)
115144
if schema_classes:
116-
schema_classes = list(
117-
{*schema_classes, *_handle_pydantic_schema(new_open_api)}
118-
)
145+
schema_classes = list({*schema_classes, *extracted_schema_classes})
119146
else:
120147
schema_classes = extracted_schema_classes
121148

openapi_pydantic/v3/v3_1_0/header.py

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
from typing import TYPE_CHECKING
2-
3-
from pydantic import Field
4-
51
from openapi_pydantic.compat import PYDANTIC_V2, ConfigDict, Extra
62

7-
from .parameter import Parameter, ParameterLocation
3+
from .parameter import ParameterBase
84

95
_examples = [
106
{
@@ -13,49 +9,27 @@
139
}
1410
]
1511

16-
if TYPE_CHECKING:
17-
18-
class Header(Parameter):
19-
"""
20-
The Header Object follows the structure of the
21-
[Parameter Object](#parameterObject) with the following changes:
22-
23-
1. `name` MUST NOT be specified, it is given in the corresponding
24-
`headers` map.
25-
2. `in` MUST NOT be specified, it is implicitly in `header`.
26-
3. All traits that are affected by the location MUST be applicable
27-
to a location of `header` (for example, [`style`](#parameterStyle)).
28-
"""
29-
30-
name: str = Field(default="")
31-
param_in: ParameterLocation = Field(
32-
default=ParameterLocation.HEADER, alias="in"
33-
)
34-
35-
elif PYDANTIC_V2:
36-
from typing import Literal
3712

38-
LiteralEmptyString = Literal[""]
13+
class Header(ParameterBase):
14+
"""
15+
The Header Object follows the structure of the
16+
[Parameter Object](#parameterObject) with the following changes:
3917
40-
class Header(Parameter):
41-
name: LiteralEmptyString = Field(default="")
42-
param_in: Literal[ParameterLocation.HEADER] = Field(
43-
default=ParameterLocation.HEADER, alias="in"
44-
)
18+
1. `name` MUST NOT be specified, it is given in the corresponding
19+
`headers` map.
20+
2. `in` MUST NOT be specified, it is implicitly in `header`.
21+
3. All traits that are affected by the location MUST be applicable
22+
to a location of `header` (for example, [`style`](#parameterStyle)).
23+
"""
4524

25+
if PYDANTIC_V2:
4626
model_config = ConfigDict(
4727
extra="allow",
4828
populate_by_name=True,
4929
json_schema_extra={"examples": _examples},
5030
)
5131

52-
else:
53-
54-
class Header(Parameter):
55-
name: str = Field(default="", const=True)
56-
param_in: ParameterLocation = Field(
57-
default=ParameterLocation.HEADER, const=True, alias="in"
58-
)
32+
else:
5933

6034
class Config:
6135
extra = Extra.allow

0 commit comments

Comments
 (0)