Skip to content

fix!: oai schema validation false negatives (vendored openapi_schema_pydantic update) #426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3 changes: 2 additions & 1 deletion openapi_python_client/parser/properties/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ def build_union_property(
*, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config
) -> Tuple[Union[UnionProperty, PropertyError], Schemas]:
sub_properties: List[Property] = []

for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)):
sub_prop, schemas = property_from_data(
name=f"{name}_type{i}",
Expand Down Expand Up @@ -439,8 +440,8 @@ def _property_from_data(
if isinstance(data, oai.Reference):
return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas)

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

Expand Down
2 changes: 1 addition & 1 deletion openapi_python_client/parser/properties/model_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]:
return prop_or_error

unprocessed_props = data.properties or {}
for sub_prop in data.allOf or []:
for sub_prop in data.allOf:
if isinstance(sub_prop, oai.Reference):
ref_path = parse_reference_path(sub_prop.ref)
if isinstance(ref_path, ParseError):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
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.

Included vendored version is the [following](https://github.com/kuimono/openapi-schema-pydantic/commit/0836b429086917feeb973de3367a7ac4c2b3a665)
Small patches has been applied to it.

## Alias

Due to the reserved words in python and pydantic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@
"ServerVariable",
"Tag",
"XML",
"Callback",
]


from .callback import Callback
from .components import Components
from .contact import Contact
from .discriminator import Discriminator
Expand Down
22 changes: 22 additions & 0 deletions openapi_python_client/schema/openapi_schema_pydantic/callback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import TYPE_CHECKING, Dict

if TYPE_CHECKING:
from .path_item import PathItem

Callback = Dict[str, "PathItem"]
"""
A map of possible out-of band callbacks related to the parent operation.
Each value in the map is a [Path Item Object](#pathItemObject)
that describes a set of requests that may be initiated by the API provider and the expected responses.
The key value used to identify the path item object is an expression, evaluated at runtime,
that identifies a URL to use for the callback operation.
"""

"""Patterned Fields"""

# {expression}: 'PathItem' = ...
"""
A Path Item Object used to define a callback request and expected responses.

A [complete example](../examples/v3.0/callback-example.yaml) is available.
"""
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Dict, Optional, Union

from pydantic import BaseModel
from pydantic import BaseModel, Extra

from .callback import Callback
from .example import Example
from .header import Header
from .link import Link
Expand Down Expand Up @@ -44,7 +45,11 @@ class Components(BaseModel):
links: Optional[Dict[str, Union[Link, Reference]]] = None
"""An object to hold reusable [Link Objects](#linkObject)."""

callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None
"""An object to hold reusable [Callback Objects](#callbackObject)."""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from pydantic import AnyUrl, BaseModel
from pydantic import AnyUrl, BaseModel, Extra


class Contact(BaseModel):
Expand All @@ -26,6 +26,7 @@ class Contact(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{"name": "API Support", "url": "http://www.example.com/support", "email": "support@example.com"}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Dict, Optional

from pydantic import BaseModel
from pydantic import BaseModel, Extra


class Discriminator(BaseModel):
Expand All @@ -25,6 +25,7 @@ class Discriminator(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{
Expand Down
10 changes: 7 additions & 3 deletions openapi_python_client/schema/openapi_schema_pydantic/encoding.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from typing import Dict, Optional
from typing import TYPE_CHECKING, Dict, Optional, Union

from pydantic import BaseModel
from pydantic import BaseModel, Extra

from .reference import Reference

if TYPE_CHECKING:
from .header import Header


class Encoding(BaseModel):
"""A single encoding definition applied to a single schema property."""
Expand All @@ -22,7 +25,7 @@ class Encoding(BaseModel):
or a comma-separated list of the two types.
"""

headers: Optional[Dict[str, Reference]] = None
headers: Optional[Dict[str, Union["Header", Reference]]] = None
"""
A map allowing additional information to be provided as headers, for example `Content-Disposition`.

Expand Down Expand Up @@ -60,6 +63,7 @@ class Encoding(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Optional

from pydantic import BaseModel
from pydantic import BaseModel, Extra


class Example(BaseModel):
Expand Down Expand Up @@ -33,6 +33,7 @@ class Example(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{"summary": "A foo example", "value": {"foo": "bar"}},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from pydantic import AnyUrl, BaseModel
from pydantic import AnyUrl, BaseModel, Extra


class ExternalDocumentation(BaseModel):
Expand All @@ -19,4 +19,5 @@ class ExternalDocumentation(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {"examples": [{"description": "Find more info here", "url": "https://example.com"}]}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pydantic import Field
from pydantic import Extra, Field

from ..parameter_location import ParameterLocation
from .parameter import Parameter
Expand All @@ -18,6 +18,7 @@ class Header(Parameter):
param_in = Field(default=ParameterLocation.HEADER, const=True, alias="in")

class Config:
extra = Extra.allow
allow_population_by_field_name = True
schema_extra = {
"examples": [
Expand Down
3 changes: 2 additions & 1 deletion openapi_python_client/schema/openapi_schema_pydantic/info.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from pydantic import AnyUrl, BaseModel
from pydantic import AnyUrl, BaseModel, Extra

from .contact import Contact
from .license import License
Expand Down Expand Up @@ -47,6 +47,7 @@ class Info(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from pydantic import AnyUrl, BaseModel
from pydantic import AnyUrl, BaseModel, Extra


class License(BaseModel):
Expand All @@ -20,4 +20,5 @@ class License(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {"examples": [{"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}]}
3 changes: 2 additions & 1 deletion openapi_python_client/schema/openapi_schema_pydantic/link.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict, Optional

from pydantic import BaseModel
from pydantic import BaseModel, Extra

from .server import Server

Expand Down Expand Up @@ -63,6 +63,7 @@ class Link(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{"operationId": "getUserAddressByUUID", "parameters": {"userUuid": "$response.body#/uuid"}},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict, Optional, Union

from pydantic import BaseModel, Field
from pydantic import BaseModel, Extra, Field

from .encoding import Encoding
from .example import Example
Expand Down Expand Up @@ -49,6 +49,7 @@ class MediaType(BaseModel):
"""

class Config:
extra = Extra.allow
allow_population_by_field_name = True
schema_extra = {
"examples": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Dict, Optional

from pydantic import AnyUrl, BaseModel
from pydantic import AnyUrl, BaseModel, Extra


class OAuthFlow(BaseModel):
Expand All @@ -15,7 +15,7 @@ class OAuthFlow(BaseModel):
This MUST be in the form of a URL.
"""

tokenUrl: Optional[str] = None
tokenUrl: Optional[AnyUrl] = None
"""
**REQUIRED** for `oauth2 ("password", "clientCredentials", "authorizationCode")`.
The token URL to be used for this flow.
Expand All @@ -35,6 +35,7 @@ class OAuthFlow(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from pydantic import BaseModel
from pydantic import BaseModel, Extra

from .oauth_flow import OAuthFlow

Expand Down Expand Up @@ -33,3 +33,6 @@ class OAuthFlows(BaseModel):

Previously called `accessCode` in OpenAPI 2.0.
"""

class Config:
extra = Extra.allow
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Optional

from pydantic import BaseModel
from pydantic import BaseModel, Extra

from .components import Components
from .external_documentation import ExternalDocumentation
Expand Down Expand Up @@ -58,3 +58,6 @@ class OpenAPI(BaseModel):
"""
Additional external documentation.
"""

class Config:
extra = Extra.allow
14 changes: 12 additions & 2 deletions openapi_python_client/schema/openapi_schema_pydantic/operation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import List, Optional, Union
from typing import Dict, List, Optional, Union

from pydantic import BaseModel
from pydantic import BaseModel, Extra

from .callback import Callback
from .external_documentation import ExternalDocumentation
from .parameter import Parameter
from .reference import Reference
Expand Down Expand Up @@ -70,6 +71,14 @@ class Operation(BaseModel):
**REQUIRED**. The list of possible responses as they are returned from executing this operation.
"""

callbacks: Optional[Dict[str, Callback]] = None
"""
A map of possible out-of band callbacks related to the parent operation.
The key is a unique identifier for the Callback Object.
Each value in the map is a [Callback Object](#callbackObject)
that describes a request that may be initiated by the API provider and the expected responses.
"""

deprecated: bool = False
"""
Declares this operation to be deprecated.
Expand All @@ -95,6 +104,7 @@ class Operation(BaseModel):
"""

class Config:
extra = Extra.allow
schema_extra = {
"examples": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any, Dict, Optional, Union

from pydantic import BaseModel, Field
from pydantic import BaseModel, Extra, Field

from ..parameter_location import ParameterLocation
from .example import Example
Expand All @@ -26,7 +26,7 @@ class Parameter(BaseModel):
- If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring
within the [path](#pathsPath) field in the [Paths Object](#pathsObject).
See [Path Templating](#pathTemplating) for further information.
- If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`.
- If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`,
the parameter definition SHALL be ignored.
- For all other cases, the `name` corresponds to the parameter name used by the [`in`](#parameterIn) property.
"""
Expand Down Expand Up @@ -142,6 +142,7 @@ class Parameter(BaseModel):
"""

class Config:
extra = Extra.allow
allow_population_by_field_name = True
schema_extra = {
"examples": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Optional, Union

from pydantic import BaseModel, Field
from pydantic import BaseModel, Extra, Field

from .operation import Operation
from .parameter import Parameter
Expand Down Expand Up @@ -92,6 +92,7 @@ class PathItem(BaseModel):
"""

class Config:
extra = Extra.allow
allow_population_by_field_name = True
schema_extra = {
"examples": [
Expand Down
Loading