Skip to content

feat!: Add an override when creating schemas that include oai.References #1003

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 70 additions & 16 deletions end_to_end_tests/baseline_openapi_3.0.json
Original file line number Diff line number Diff line change
@@ -1415,7 +1415,9 @@
},
"/naming/mixed-case": {
"get": {
"tags": ["naming"],
"tags": [
"naming"
],
"operationId": "mixed_case",
"parameters": [
{
@@ -1436,30 +1438,32 @@
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mixed_case": {
"type": "string"
},
"mixedCase": {
"type": "string"
}
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mixed_case": {
"type": "string"
},
"mixedCase": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/naming/{hyphen-in-path}": {
"get": {
"tags": ["naming"],
"tags": [
"naming"
],
"operationId": "hyphen_in_path",
"parameters": [
{
@@ -2643,6 +2647,56 @@
"type": "string"
}
}
},
"OneOfBlockWithReferences": {
"oneOf": [
{
"type": "string",
"description": "OneOfBlockOneDescription",
"enum": [
"OneOfBlockOne"
]
},
{
"type": "object",
"required": [
"OneOfBlockTwo"
],
"properties": {
"OneOfBlockTwo": {
"$ref": "#/components/schemas/OneOfBlockTwo"
}
}
},
{
"type": "object",
"required": [
"OneOfBlockThree"
],
"properties": {
"OneOfBlockThree": {
"$ref": "#/components/schemas/OneOfBlockThree"
}
}
}
]
},
"OneOfBlockTwo": {
"type": "integer",
"format": "int32",
"description": "OneOfBlockTwoDescription",
"minimum": 0
},
"OneOfBlockThree": {
"$ref": "#/components/schemas/OneOfBlockThreeRef"
},
"OneOfBlockThreeRef": {
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"description": "OneOfBlockThreeRefDescription"
}
},
"parameters": {
@@ -2711,4 +2765,4 @@
}
}
}
}
}
5,056 changes: 2,378 additions & 2,678 deletions end_to_end_tests/baseline_openapi_3.1.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from enum import Enum


class OneOfBlockWithReferencesType0(str, Enum):
ONEOFBLOCKONE = "OneOfBlockOne"

def __str__(self) -> str:
return str(self.value)
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import Any, Dict, List, Type, TypeVar

from attrs import define as _attrs_define
from attrs import field as _attrs_field

T = TypeVar("T", bound="OneOfBlockWithReferencesType1")


@_attrs_define
class OneOfBlockWithReferencesType1:
"""
Attributes:
one_of_block_two (int): OneOfBlockTwoDescription
"""

one_of_block_two: int
additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict)

def to_dict(self) -> Dict[str, Any]:
one_of_block_two = self.one_of_block_two

field_dict: Dict[str, Any] = {}
field_dict.update(self.additional_properties)
field_dict.update(
{
"OneOfBlockTwo": one_of_block_two,
}
)

return field_dict

@classmethod
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
d = src_dict.copy()
one_of_block_two = d.pop("OneOfBlockTwo")

one_of_block_with_references_type_1 = cls(
one_of_block_two=one_of_block_two,
)

one_of_block_with_references_type_1.additional_properties = d
return one_of_block_with_references_type_1

@property
def additional_keys(self) -> List[str]:
return list(self.additional_properties.keys())

def __getitem__(self, key: str) -> Any:
return self.additional_properties[key]

def __setitem__(self, key: str, value: Any) -> None:
self.additional_properties[key] = value

def __delitem__(self, key: str) -> None:
del self.additional_properties[key]

def __contains__(self, key: str) -> bool:
return key in self.additional_properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import Any, Dict, List, Type, TypeVar, cast

from attrs import define as _attrs_define
from attrs import field as _attrs_field

T = TypeVar("T", bound="OneOfBlockWithReferencesType2")


@_attrs_define
class OneOfBlockWithReferencesType2:
"""
Attributes:
one_of_block_three (List[float]): OneOfBlockThreeRefDescription
"""

one_of_block_three: List[float]
additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict)

def to_dict(self) -> Dict[str, Any]:
one_of_block_three = self.one_of_block_three

field_dict: Dict[str, Any] = {}
field_dict.update(self.additional_properties)
field_dict.update(
{
"OneOfBlockThree": one_of_block_three,
}
)

return field_dict

@classmethod
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
d = src_dict.copy()
one_of_block_three = cast(List[float], d.pop("OneOfBlockThree"))

one_of_block_with_references_type_2 = cls(
one_of_block_three=one_of_block_three,
)

one_of_block_with_references_type_2.additional_properties = d
return one_of_block_with_references_type_2

@property
def additional_keys(self) -> List[str]:
return list(self.additional_properties.keys())

def __getitem__(self, key: str) -> Any:
return self.additional_properties[key]

def __setitem__(self, key: str, value: Any) -> None:
self.additional_properties[key] = value

def __delitem__(self, key: str) -> None:
del self.additional_properties[key]

def __contains__(self, key: str) -> bool:
return key in self.additional_properties
12 changes: 9 additions & 3 deletions openapi_python_client/parser/properties/__init__.py
Original file line number Diff line number Diff line change
@@ -289,6 +289,7 @@ def _create_schemas(
to_process: Iterable[tuple[str, oai.Reference | oai.Schema]] = components.items()
still_making_progress = True
errors: list[PropertyError] = []
ref_override = None

# References could have forward References so keep going as long as we are making progress
while still_making_progress:
@@ -298,13 +299,18 @@ def _create_schemas(
# Only accumulate errors from the last round, since we might fix some along the way
for name, data in to_process:
if isinstance(data, oai.Reference):
schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported."))
continue
ref_override = parse_reference_path(data.ref)
ref_path = parse_reference_path(f"#/components/schemas/{name}")
if isinstance(ref_path, ParseError):
schemas.errors.append(PropertyError(detail=ref_path.detail, data=data))
continue
schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config)
schemas_or_err = update_schemas_with_data(
ref_path=ref_path,
data=data,
schemas=schemas,
config=config,
ref_override=ref_override,
)
if isinstance(schemas_or_err, PropertyError):
next_round.append((name, data))
errors.append(schemas_or_err)
9 changes: 7 additions & 2 deletions openapi_python_client/parser/properties/schemas.py
Original file line number Diff line number Diff line change
@@ -91,7 +91,12 @@ def add_dependencies(self, ref_path: ReferencePath, roots: Set[Union[ReferencePa


def update_schemas_with_data(
*, ref_path: ReferencePath, data: oai.Schema, schemas: Schemas, config: Config
*,
ref_path: ReferencePath,
data: oai.Schema,
schemas: Schemas,
config: Config,
ref_override: ReferencePath = None,
) -> Union[Schemas, PropertyError]:
"""
Update a `Schemas` using some new reference.
@@ -120,7 +125,7 @@ def update_schemas_with_data(
config=config,
# Don't process ModelProperty properties because schemas are still being created
process_properties=False,
roots={ref_path},
roots={ref_path if ref_override is None else ref_override},
)

if isinstance(prop, PropertyError):
3 changes: 2 additions & 1 deletion tests/test_parser/test_properties/test_init.py
Original file line number Diff line number Diff line change
@@ -858,6 +858,7 @@ def test_skips_references_and_keeps_going(self, mocker, config):
schemas=Schemas(
errors=[PropertyError(detail="Reference schemas are not supported.", data=components["a_ref"])]
),
ref_override=None,
)
assert result == update_schemas_with_data.return_value

@@ -883,7 +884,7 @@ def test_records_bad_uris_and_keeps_going(self, mocker, config):
ref_path="a_path",
config=config,
data=components["second"],
schemas=Schemas(errors=[PropertyError(detail="some details", data=components["first"])]),
ref_override=None,
)
assert result == update_schemas_with_data.return_value