Skip to content

Commit 0101a03

Browse files
authored
fix: OpenAPI schema validation issues (#426, #568). Thanks @p1-ra!
BREAKING CHANGE: Validation of OpenAPI documents is now more strict. Co-authored-by: Nementon <[email protected]> Co-authored-by: p1-ra <[email protected]>
1 parent 821dac8 commit 0101a03

31 files changed

+99
-35
lines changed

Diff for: openapi_python_client/parser/properties/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ def build_union_property(
453453
constructed `UnionProperty` or a `PropertyError` describing what went wrong.
454454
"""
455455
sub_properties: List[Property] = []
456+
456457
for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)):
457458
sub_prop, schemas = property_from_data(
458459
name=f"{name}_type_{i}",
@@ -570,8 +571,8 @@ def _property_from_data(
570571
if isinstance(data, oai.Reference):
571572
return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas, config=config)
572573

574+
sub_data: List[Union[oai.Schema, oai.Reference]] = data.allOf + data.anyOf + data.oneOf
573575
# A union of a single reference should just be passed through to that reference (don't create copy class)
574-
sub_data = (data.allOf or []) + data.anyOf + data.oneOf
575576
if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference):
576577
return _property_from_ref(
577578
name=name, required=required, parent=data, data=sub_data[0], schemas=schemas, config=config

Diff for: openapi_python_client/parser/properties/model_property.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def _add_if_no_conflict(new_prop: Property) -> Optional[PropertyError]:
135135
return None
136136

137137
unprocessed_props = data.properties or {}
138-
for sub_prop in data.allOf or []:
138+
for sub_prop in data.allOf:
139139
if isinstance(sub_prop, oai.Reference):
140140
ref_path = parse_reference_path(sub_prop.ref)
141141
if isinstance(ref_path, ParseError):

Diff for: openapi_python_client/schema/openapi_schema_pydantic/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
Everything in this directory (including the rest of this file after this paragraph) is a vendored copy of [openapi-schem-pydantic](https://github.com/kuimono/openapi-schema-pydantic) and is licensed under the LICENSE file in this directory.
22

3+
Included vendored version is the [following](https://github.com/kuimono/openapi-schema-pydantic/commit/0836b429086917feeb973de3367a7ac4c2b3a665)
4+
Small patches has been applied to it.
5+
36
## Alias
47

58
Due to the reserved words in python and pydantic,

Diff for: openapi_python_client/schema/openapi_schema_pydantic/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@
3636
"ServerVariable",
3737
"Tag",
3838
"XML",
39+
"Callback",
3940
]
4041

42+
43+
from .callback import Callback
4144
from .components import Components
4245
from .contact import Contact
4346
from .discriminator import Discriminator
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import TYPE_CHECKING, Dict
2+
3+
if TYPE_CHECKING: # pragma: no cover
4+
from .path_item import PathItem
5+
else:
6+
PathItem = "PathItem" # pylint: disable=invalid-name
7+
8+
Callback = Dict[str, PathItem]
9+
"""
10+
A map of possible out-of band callbacks related to the parent operation.
11+
Each value in the map is a [Path Item Object](#pathItemObject)
12+
that describes a set of requests that may be initiated by the API provider and the expected responses.
13+
The key value used to identify the path item object is an expression, evaluated at runtime,
14+
that identifies a URL to use for the callback operation.
15+
"""

Diff for: openapi_python_client/schema/openapi_schema_pydantic/components.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import Dict, Optional, Union
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

5+
from .callback import Callback
56
from .example import Example
67
from .header import Header
78
from .link import Link
@@ -32,8 +33,10 @@ class Components(BaseModel):
3233
headers: Optional[Dict[str, Union[Header, Reference]]] = None
3334
securitySchemes: Optional[Dict[str, Union[SecurityScheme, Reference]]] = None
3435
links: Optional[Dict[str, Union[Link, Reference]]] = None
36+
callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None
3537

3638
class Config: # pylint: disable=missing-class-docstring
39+
extra = Extra.allow
3740
schema_extra = {
3841
"examples": [
3942
{

Diff for: openapi_python_client/schema/openapi_schema_pydantic/contact.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from pydantic import AnyUrl, BaseModel
3+
from pydantic import AnyUrl, BaseModel, Extra
44

55

66
class Contact(BaseModel):
@@ -16,6 +16,7 @@ class Contact(BaseModel):
1616
email: Optional[str] = None
1717

1818
class Config: # pylint: disable=missing-class-docstring
19+
extra = Extra.allow
1920
schema_extra = {
2021
"examples": [
2122
{"name": "API Support", "url": "http://www.example.com/support", "email": "[email protected]"}

Diff for: openapi_python_client/schema/openapi_schema_pydantic/discriminator.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Dict, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

55

66
class Discriminator(BaseModel):
@@ -22,6 +22,7 @@ class Discriminator(BaseModel):
2222
mapping: Optional[Dict[str, str]] = None
2323

2424
class Config: # pylint: disable=missing-class-docstring
25+
extra = Extra.allow
2526
schema_extra = {
2627
"examples": [
2728
{

Diff for: openapi_python_client/schema/openapi_schema_pydantic/encoding.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
from typing import Dict, Optional
1+
from typing import TYPE_CHECKING, Dict, Optional, Union
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

55
from .reference import Reference
66

7+
if TYPE_CHECKING: # pragma: no cover
8+
from .header import Header
9+
else:
10+
Header = "Header" # pylint: disable=invalid-name
11+
712

813
class Encoding(BaseModel):
914
"""A single encoding definition applied to a single schema property.
@@ -14,12 +19,13 @@ class Encoding(BaseModel):
1419
"""
1520

1621
contentType: Optional[str] = None
17-
headers: Optional[Dict[str, Reference]] = None
22+
headers: Optional[Dict[str, Union[Header, Reference]]] = None
1823
style: Optional[str] = None
1924
explode: bool = False
2025
allowReserved: bool = False
2126

2227
class Config: # pylint: disable=missing-class-docstring
28+
extra = Extra.allow
2329
schema_extra = {
2430
"examples": [
2531
{

Diff for: openapi_python_client/schema/openapi_schema_pydantic/example.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

55

66
class Example(BaseModel):
@@ -17,6 +17,7 @@ class Example(BaseModel):
1717
externalValue: Optional[str] = None
1818

1919
class Config: # pylint: disable=missing-class-docstring
20+
extra = Extra.allow
2021
schema_extra = {
2122
"examples": [
2223
{"summary": "A foo example", "value": {"foo": "bar"}},

Diff for: openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from pydantic import AnyUrl, BaseModel
3+
from pydantic import AnyUrl, BaseModel, Extra
44

55

66
class ExternalDocumentation(BaseModel):
@@ -14,4 +14,5 @@ class ExternalDocumentation(BaseModel):
1414
url: AnyUrl
1515

1616
class Config: # pylint: disable=missing-class-docstring
17+
extra = Extra.allow
1718
schema_extra = {"examples": [{"description": "Find more info here", "url": "https://example.com"}]}

Diff for: openapi_python_client/schema/openapi_schema_pydantic/header.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pydantic import Field
1+
from pydantic import Extra, Field
22

33
from ..parameter_location import ParameterLocation
44
from .parameter import Parameter
@@ -22,6 +22,7 @@ class Header(Parameter):
2222
param_in = Field(default=ParameterLocation.HEADER, const=True, alias="in")
2323

2424
class Config: # pylint: disable=missing-class-docstring
25+
extra = Extra.allow
2526
allow_population_by_field_name = True
2627
schema_extra = {
2728
"examples": [

Diff for: openapi_python_client/schema/openapi_schema_pydantic/info.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from pydantic import AnyUrl, BaseModel
3+
from pydantic import AnyUrl, BaseModel, Extra
44

55
from .contact import Contact
66
from .license import License
@@ -25,6 +25,7 @@ class Info(BaseModel):
2525
version: str
2626

2727
class Config: # pylint: disable=missing-class-docstring
28+
extra = Extra.allow
2829
schema_extra = {
2930
"examples": [
3031
{

Diff for: openapi_python_client/schema/openapi_schema_pydantic/license.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from pydantic import AnyUrl, BaseModel
3+
from pydantic import AnyUrl, BaseModel, Extra
44

55

66
class License(BaseModel):
@@ -15,4 +15,5 @@ class License(BaseModel):
1515
url: Optional[AnyUrl] = None
1616

1717
class Config: # pylint: disable=missing-class-docstring
18+
extra = Extra.allow
1819
schema_extra = {"examples": [{"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}]}

Diff for: openapi_python_client/schema/openapi_schema_pydantic/link.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Dict, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

55
from .server import Server
66

@@ -31,6 +31,7 @@ class Link(BaseModel):
3131
server: Optional[Server] = None
3232

3333
class Config: # pylint: disable=missing-class-docstring
34+
extra = Extra.allow
3435
schema_extra = {
3536
"examples": [
3637
{"operationId": "getUserAddressByUUID", "parameters": {"userUuid": "$response.body#/uuid"}},

Diff for: openapi_python_client/schema/openapi_schema_pydantic/media_type.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Dict, Optional, Union
22

3-
from pydantic import BaseModel, Field
3+
from pydantic import BaseModel, Extra, Field
44

55
from .encoding import Encoding
66
from .example import Example
@@ -22,6 +22,7 @@ class MediaType(BaseModel):
2222
encoding: Optional[Dict[str, Encoding]] = None
2323

2424
class Config: # pylint: disable=missing-class-docstring
25+
extra = Extra.allow
2526
allow_population_by_field_name = True
2627
schema_extra = {
2728
"examples": [

Diff for: openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Dict, Optional
22

3-
from pydantic import AnyUrl, BaseModel
3+
from pydantic import AnyUrl, BaseModel, Extra
44

55

66
class OAuthFlow(BaseModel):
@@ -13,11 +13,12 @@ class OAuthFlow(BaseModel):
1313
"""
1414

1515
authorizationUrl: Optional[AnyUrl] = None
16-
tokenUrl: Optional[str] = None
16+
tokenUrl: Optional[AnyUrl] = None
1717
refreshUrl: Optional[AnyUrl] = None
1818
scopes: Dict[str, str]
1919

2020
class Config: # pylint: disable=missing-class-docstring
21+
extra = Extra.allow
2122
schema_extra = {
2223
"examples": [
2324
{

Diff for: openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

55
from .oauth_flow import OAuthFlow
66

@@ -18,3 +18,6 @@ class OAuthFlows(BaseModel):
1818
password: Optional[OAuthFlow] = None
1919
clientCredentials: Optional[OAuthFlow] = None
2020
authorizationCode: Optional[OAuthFlow] = None
21+
22+
class Config: # pylint: disable=missing-class-docstring
23+
extra = Extra.allow

Diff for: openapi_python_client/schema/openapi_schema_pydantic/open_api.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
from typing import List, Optional, Union
44

5-
from pydantic import BaseModel
5+
from pydantic import BaseModel, Extra
66

77
from .components import Components
88
from .external_documentation import ExternalDocumentation
@@ -34,3 +34,6 @@ class OpenAPI(BaseModel):
3434
tags: Optional[List[Tag]] = None
3535
externalDocs: Optional[ExternalDocumentation] = None
3636
openapi: 'Union[Literal["3.0.0"], Literal["3.0.1"], Literal["3.0.2"], Literal["3.0.3"]]'
37+
38+
class Config: # pylint: disable=missing-class-docstring
39+
extra = Extra.allow

Diff for: openapi_python_client/schema/openapi_schema_pydantic/operation.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import List, Optional, Union
1+
from typing import Dict, List, Optional, Union
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

5+
from .callback import Callback
56
from .external_documentation import ExternalDocumentation
67
from .parameter import Parameter
78
from .reference import Reference
@@ -27,11 +28,14 @@ class Operation(BaseModel):
2728
parameters: Optional[List[Union[Parameter, Reference]]] = None
2829
requestBody: Optional[Union[RequestBody, Reference]] = None
2930
responses: Responses
31+
callbacks: Optional[Dict[str, Callback]] = None
32+
3033
deprecated: bool = False
3134
security: Optional[List[SecurityRequirement]] = None
3235
servers: Optional[List[Server]] = None
3336

3437
class Config: # pylint: disable=missing-class-docstring
38+
extra = Extra.allow
3539
schema_extra = {
3640
"examples": [
3741
{

Diff for: openapi_python_client/schema/openapi_schema_pydantic/parameter.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Dict, Optional, Union
22

3-
from pydantic import BaseModel, Field
3+
from pydantic import BaseModel, Extra, Field
44

55
from ..parameter_location import ParameterLocation
66
from .example import Example
@@ -36,6 +36,7 @@ class Parameter(BaseModel):
3636
content: Optional[Dict[str, MediaType]] = None
3737

3838
class Config: # pylint: disable=missing-class-docstring
39+
extra = Extra.allow
3940
allow_population_by_field_name = True
4041
schema_extra = {
4142
"examples": [

Diff for: openapi_python_client/schema/openapi_schema_pydantic/path_item.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List, Optional, Union
22

3-
from pydantic import BaseModel, Field
3+
from pydantic import BaseModel, Extra, Field
44

55
from .operation import Operation
66
from .parameter import Parameter
@@ -35,6 +35,7 @@ class PathItem(BaseModel):
3535
parameters: Optional[List[Union[Parameter, Reference]]] = None
3636

3737
class Config: # pylint: disable=missing-class-docstring
38+
extra = Extra.allow
3839
allow_population_by_field_name = True
3940
schema_extra = {
4041
"examples": [

Diff for: openapi_python_client/schema/openapi_schema_pydantic/reference.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pydantic import BaseModel, Field
1+
from pydantic import BaseModel, Extra, Field
22

33

44
class Reference(BaseModel):
@@ -19,6 +19,7 @@ class Reference(BaseModel):
1919
ref: str = Field(alias="$ref")
2020

2121
class Config: # pylint: disable=missing-class-docstring
22+
extra = Extra.allow
2223
allow_population_by_field_name = True
2324
schema_extra = {
2425
"examples": [{"$ref": "#/components/schemas/Pet"}, {"$ref": "Pet.json"}, {"$ref": "definitions.json#/Pet"}]

Diff for: openapi_python_client/schema/openapi_schema_pydantic/request_body.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Dict, Optional
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Extra
44

55
from .media_type import MediaType
66

@@ -18,6 +18,7 @@ class RequestBody(BaseModel):
1818
required: bool = False
1919

2020
class Config: # pylint: disable=missing-class-docstring
21+
extra = Extra.allow
2122
schema_extra = {
2223
"examples": [
2324
{

0 commit comments

Comments
 (0)