Skip to content

Commit

Permalink
Properly rebuild Pydantic models if necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
Viicos committed Dec 5, 2024
1 parent 861ef56 commit 9f75014
Show file tree
Hide file tree
Showing 13 changed files with 49 additions and 27 deletions.
10 changes: 10 additions & 0 deletions .changeset/properly_rebuild_pydantic_models_if_necessary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
default: CHANGE_TYPE
---

# Properly rebuild Pydantic models if necessary

#1176 by @Viicos

Set `defer_build` to models that we know will fail to build, and call `model_rebuild`
in the `__init__.py` file.
11 changes: 11 additions & 0 deletions openapi_python_client/schema/openapi_schema_pydantic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,14 @@
from .server_variable import ServerVariable
from .tag import Tag
from .xml import XML

PathItem.model_rebuild()
Operation.model_rebuild()
Components.model_rebuild()
Encoding.model_rebuild()
MediaType.model_rebuild()
OpenAPI.model_rebuild()
Parameter.model_rebuild()
Header.model_rebuild()
RequestBody.model_rebuild()
Response.model_rebuild()
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class Components(BaseModel):
links: Optional[dict[str, Union[Link, Reference]]] = None
callbacks: Optional[dict[str, Union[Callback, Reference]]] = None
model_config = ConfigDict(
# `Callback` contains an unresolvable forward reference, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
json_schema_extra={
"examples": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

if TYPE_CHECKING: # pragma: no cover
from .header import Header
else:
Header = "Header"


class Encoding(BaseModel):
Expand All @@ -19,11 +17,13 @@ class Encoding(BaseModel):
"""

contentType: Optional[str] = None
headers: Optional[dict[str, Union[Header, Reference]]] = None
headers: Optional[dict[str, Union["Header", Reference]]] = None
style: Optional[str] = None
explode: bool = False
allowReserved: bool = False
model_config = ConfigDict(
# `Header` is an unresolvable forward reference, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
json_schema_extra={
"examples": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class Header(Parameter):
name: str = Field(default="")
param_in: ParameterLocation = Field(default=ParameterLocation.HEADER, alias="in")
model_config = ConfigDict(
# `Parameter` is not build yet, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
populate_by_name=True,
json_schema_extra={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class MediaType(BaseModel):
examples: Optional[dict[str, Union[Example, Reference]]] = None
encoding: Optional[dict[str, Encoding]] = None
model_config = ConfigDict(
# `Encoding` is not build yet, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
populate_by_name=True,
json_schema_extra={
Expand Down
12 changes: 5 additions & 7 deletions openapi_python_client/schema/openapi_schema_pydantic/open_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
from .components import Components
from .external_documentation import ExternalDocumentation
from .info import Info

# Required to update forward ref after object creation
from .path_item import PathItem # noqa: F401
from .paths import Paths
from .security_requirement import SecurityRequirement
from .server import Server
Expand All @@ -32,7 +29,11 @@ class OpenAPI(BaseModel):
tags: Optional[list[Tag]] = None
externalDocs: Optional[ExternalDocumentation] = None
openapi: str
model_config = ConfigDict(extra="allow")
model_config = ConfigDict(
# `Components` is not build yet, will rebuild in `__init__.py`:
defer_build=True,
extra="allow"
)

@field_validator("openapi")
@classmethod
Expand All @@ -46,6 +47,3 @@ def check_openapi_version(cls, value: str) -> str:
if int(parts[1]) > 1:
raise ValueError(f"Only OpenAPI versions 3.1.* are supported, got {value}")
return value


OpenAPI.model_rebuild()
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@

from .callback import Callback
from .external_documentation import ExternalDocumentation
from .header import Header # noqa: F401
from .parameter import Parameter

# Required to update forward ref after object creation, as this is not imported yet
from .path_item import PathItem # noqa: F401

from .reference import Reference
from .request_body import RequestBody
from .responses import Responses
Expand Down Expand Up @@ -38,6 +36,8 @@ class Operation(BaseModel):
security: Optional[list[SecurityRequirement]] = None
servers: Optional[list[Server]] = None
model_config = ConfigDict(
# `Callback` contains an unresolvable forward reference, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
json_schema_extra={
"examples": [
Expand Down Expand Up @@ -89,7 +89,3 @@ class Operation(BaseModel):
]
},
)


# PathItem in Callback uses Operation, so we need to update forward refs due to circular dependency
Operation.model_rebuild()
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class Parameter(BaseModel):
examples: Optional[dict[str, Union[Example, Reference]]] = None
content: Optional[dict[str, MediaType]] = None
model_config = ConfigDict(
# `MediaType` is not build yet, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
populate_by_name=True,
json_schema_extra={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union

from pydantic import BaseModel, ConfigDict, Field

from .parameter import Parameter
from .reference import Reference
from .server import Server

if TYPE_CHECKING:
from .operation import Operation

class PathItem(BaseModel):
"""
Expand Down Expand Up @@ -33,6 +35,8 @@ class PathItem(BaseModel):
servers: Optional[list[Server]] = None
parameters: Optional[list[Union[Parameter, Reference]]] = None
model_config = ConfigDict(
# `Operation` is an unresolvable forward reference, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
populate_by_name=True,
json_schema_extra={
Expand Down Expand Up @@ -69,9 +73,3 @@ class PathItem(BaseModel):
]
},
)


# Operation uses PathItem via Callback, so we need late import and to update forward refs due to circular dependency
from .operation import Operation # noqa: E402

PathItem.model_rebuild()
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class RequestBody(BaseModel):
content: dict[str, MediaType]
required: bool = False
model_config = ConfigDict(
# `MediaType` is not build yet, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
json_schema_extra={
"examples": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Response(BaseModel):
content: Optional[dict[str, MediaType]] = None
links: Optional[dict[str, Union[Link, Reference]]] = None
model_config = ConfigDict(
# `MediaType` is not build yet, will rebuild in `__init__.py`:
defer_build=True,
extra="allow",
json_schema_extra={
"examples": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,3 @@ def handle_nullable(self) -> "Schema":
self.oneOf = [Schema(type=DataType.NULL), Schema(allOf=self.allOf)]
self.allOf = []
return self


Schema.model_rebuild()

0 comments on commit 9f75014

Please sign in to comment.