Skip to content

Commit 0374ce8

Browse files
committed
feat: support prefixItems for arrays
Generates a union of all types in `prefixItems` and `items` for the inner list item type
1 parent db6f4f2 commit 0374ce8

File tree

8 files changed

+410
-12
lines changed

8 files changed

+410
-12
lines changed

Diff for: end_to_end_tests/3.1_specific.openapi.yaml

+31
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,34 @@ paths:
4747
"application/json":
4848
schema:
4949
const: "Why have a fixed response? I dunno"
50+
"/prefixItems":
51+
post:
52+
tags: [ "prefixItems" ]
53+
requestBody:
54+
required: true
55+
content:
56+
"application/json":
57+
schema:
58+
type: object
59+
properties:
60+
prefixItemsAndItems:
61+
type: array
62+
prefixItems:
63+
- type: string
64+
const: "prefix"
65+
- type: string
66+
items:
67+
type: number
68+
prefixItemsOnly:
69+
type: array
70+
prefixItems:
71+
- type: string
72+
- type: number
73+
maxItems: 2
74+
responses:
75+
"200":
76+
description: "Successful Response"
77+
content:
78+
"application/json":
79+
schema:
80+
type: string

Diff for: end_to_end_tests/test-3-1-golden-record/test_3_1_features_client/api/prefix_items/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from http import HTTPStatus
2+
from typing import Any, Dict, Optional, Union, cast
3+
4+
import httpx
5+
6+
from ... import errors
7+
from ...client import AuthenticatedClient, Client
8+
from ...models.post_prefix_items_body import PostPrefixItemsBody
9+
from ...types import Response
10+
11+
12+
def _get_kwargs(
13+
*,
14+
body: PostPrefixItemsBody,
15+
) -> Dict[str, Any]:
16+
headers: Dict[str, Any] = {}
17+
18+
_kwargs: Dict[str, Any] = {
19+
"method": "post",
20+
"url": "/prefixItems",
21+
}
22+
23+
_body = body.to_dict()
24+
25+
_kwargs["json"] = _body
26+
headers["Content-Type"] = "application/json"
27+
28+
_kwargs["headers"] = headers
29+
return _kwargs
30+
31+
32+
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[str]:
33+
if response.status_code == HTTPStatus.OK:
34+
response_200 = cast(str, response.json())
35+
return response_200
36+
if client.raise_on_unexpected_status:
37+
raise errors.UnexpectedStatus(response.status_code, response.content)
38+
else:
39+
return None
40+
41+
42+
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[str]:
43+
return Response(
44+
status_code=HTTPStatus(response.status_code),
45+
content=response.content,
46+
headers=response.headers,
47+
parsed=_parse_response(client=client, response=response),
48+
)
49+
50+
51+
def sync_detailed(
52+
*,
53+
client: Union[AuthenticatedClient, Client],
54+
body: PostPrefixItemsBody,
55+
) -> Response[str]:
56+
"""
57+
Args:
58+
body (PostPrefixItemsBody):
59+
60+
Raises:
61+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
62+
httpx.TimeoutException: If the request takes longer than Client.timeout.
63+
64+
Returns:
65+
Response[str]
66+
"""
67+
68+
kwargs = _get_kwargs(
69+
body=body,
70+
)
71+
72+
response = client.get_httpx_client().request(
73+
**kwargs,
74+
)
75+
76+
return _build_response(client=client, response=response)
77+
78+
79+
def sync(
80+
*,
81+
client: Union[AuthenticatedClient, Client],
82+
body: PostPrefixItemsBody,
83+
) -> Optional[str]:
84+
"""
85+
Args:
86+
body (PostPrefixItemsBody):
87+
88+
Raises:
89+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
90+
httpx.TimeoutException: If the request takes longer than Client.timeout.
91+
92+
Returns:
93+
str
94+
"""
95+
96+
return sync_detailed(
97+
client=client,
98+
body=body,
99+
).parsed
100+
101+
102+
async def asyncio_detailed(
103+
*,
104+
client: Union[AuthenticatedClient, Client],
105+
body: PostPrefixItemsBody,
106+
) -> Response[str]:
107+
"""
108+
Args:
109+
body (PostPrefixItemsBody):
110+
111+
Raises:
112+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
113+
httpx.TimeoutException: If the request takes longer than Client.timeout.
114+
115+
Returns:
116+
Response[str]
117+
"""
118+
119+
kwargs = _get_kwargs(
120+
body=body,
121+
)
122+
123+
response = await client.get_async_httpx_client().request(**kwargs)
124+
125+
return _build_response(client=client, response=response)
126+
127+
128+
async def asyncio(
129+
*,
130+
client: Union[AuthenticatedClient, Client],
131+
body: PostPrefixItemsBody,
132+
) -> Optional[str]:
133+
"""
134+
Args:
135+
body (PostPrefixItemsBody):
136+
137+
Raises:
138+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
139+
httpx.TimeoutException: If the request takes longer than Client.timeout.
140+
141+
Returns:
142+
str
143+
"""
144+
145+
return (
146+
await asyncio_detailed(
147+
client=client,
148+
body=body,
149+
)
150+
).parsed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Contains all the data models used in inputs/outputs"""
22

33
from .post_const_path_body import PostConstPathBody
4+
from .post_prefix_items_body import PostPrefixItemsBody
45

5-
__all__ = ("PostConstPathBody",)
6+
__all__ = (
7+
"PostConstPathBody",
8+
"PostPrefixItemsBody",
9+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from typing import Any, Dict, List, Literal, Type, TypeVar, Union, cast
2+
3+
from attrs import define as _attrs_define
4+
from attrs import field as _attrs_field
5+
6+
from ..types import UNSET, Unset
7+
8+
T = TypeVar("T", bound="PostPrefixItemsBody")
9+
10+
11+
@_attrs_define
12+
class PostPrefixItemsBody:
13+
"""
14+
Attributes:
15+
prefix_items_and_items (Union[Unset, List[Union[Literal['prefix'], float, str]]]):
16+
prefix_items_only (Union[Unset, List[Union[float, str]]]):
17+
"""
18+
19+
prefix_items_and_items: Union[Unset, List[Union[Literal["prefix"], float, str]]] = UNSET
20+
prefix_items_only: Union[Unset, List[Union[float, str]]] = UNSET
21+
additional_properties: Dict[str, Any] = _attrs_field(init=False, factory=dict)
22+
23+
def to_dict(self) -> Dict[str, Any]:
24+
prefix_items_and_items: Union[Unset, List[Union[Literal["prefix"], float, str]]] = UNSET
25+
if not isinstance(self.prefix_items_and_items, Unset):
26+
prefix_items_and_items = []
27+
for prefix_items_and_items_item_data in self.prefix_items_and_items:
28+
prefix_items_and_items_item: Union[Literal["prefix"], float, str]
29+
prefix_items_and_items_item = prefix_items_and_items_item_data
30+
prefix_items_and_items.append(prefix_items_and_items_item)
31+
32+
prefix_items_only: Union[Unset, List[Union[float, str]]] = UNSET
33+
if not isinstance(self.prefix_items_only, Unset):
34+
prefix_items_only = []
35+
for prefix_items_only_item_data in self.prefix_items_only:
36+
prefix_items_only_item: Union[float, str]
37+
prefix_items_only_item = prefix_items_only_item_data
38+
prefix_items_only.append(prefix_items_only_item)
39+
40+
field_dict: Dict[str, Any] = {}
41+
field_dict.update(self.additional_properties)
42+
field_dict.update({})
43+
if prefix_items_and_items is not UNSET:
44+
field_dict["prefixItemsAndItems"] = prefix_items_and_items
45+
if prefix_items_only is not UNSET:
46+
field_dict["prefixItemsOnly"] = prefix_items_only
47+
48+
return field_dict
49+
50+
@classmethod
51+
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
52+
d = src_dict.copy()
53+
prefix_items_and_items = []
54+
_prefix_items_and_items = d.pop("prefixItemsAndItems", UNSET)
55+
for prefix_items_and_items_item_data in _prefix_items_and_items or []:
56+
57+
def _parse_prefix_items_and_items_item(data: object) -> Union[Literal["prefix"], float, str]:
58+
prefix_items_and_items_item_type_0 = cast(Literal["prefix"], data)
59+
if prefix_items_and_items_item_type_0 != "prefix":
60+
raise ValueError(
61+
f"prefixItemsAndItems_item_type_0 must match const 'prefix', got '{prefix_items_and_items_item_type_0}'"
62+
)
63+
return prefix_items_and_items_item_type_0
64+
return cast(Union[Literal["prefix"], float, str], data)
65+
66+
prefix_items_and_items_item = _parse_prefix_items_and_items_item(prefix_items_and_items_item_data)
67+
68+
prefix_items_and_items.append(prefix_items_and_items_item)
69+
70+
prefix_items_only = []
71+
_prefix_items_only = d.pop("prefixItemsOnly", UNSET)
72+
for prefix_items_only_item_data in _prefix_items_only or []:
73+
74+
def _parse_prefix_items_only_item(data: object) -> Union[float, str]:
75+
return cast(Union[float, str], data)
76+
77+
prefix_items_only_item = _parse_prefix_items_only_item(prefix_items_only_item_data)
78+
79+
prefix_items_only.append(prefix_items_only_item)
80+
81+
post_prefix_items_body = cls(
82+
prefix_items_and_items=prefix_items_and_items,
83+
prefix_items_only=prefix_items_only,
84+
)
85+
86+
post_prefix_items_body.additional_properties = d
87+
return post_prefix_items_body
88+
89+
@property
90+
def additional_keys(self) -> List[str]:
91+
return list(self.additional_properties.keys())
92+
93+
def __getitem__(self, key: str) -> Any:
94+
return self.additional_properties[key]
95+
96+
def __setitem__(self, key: str, value: Any) -> None:
97+
self.additional_properties[key] = value
98+
99+
def __delitem__(self, key: str) -> None:
100+
del self.additional_properties[key]
101+
102+
def __contains__(self, key: str) -> bool:
103+
return key in self.additional_properties

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,28 @@ def build(
5858
"""
5959
from . import property_from_data
6060

61-
if data.items is None:
62-
return PropertyError(data=data, detail="type array must have items defined"), schemas
61+
if data.items is None and not data.prefixItems:
62+
return (
63+
PropertyError(
64+
data=data,
65+
detail="type array must have items or prefixItems defined",
66+
),
67+
schemas,
68+
)
69+
70+
items = data.prefixItems or []
71+
if data.items:
72+
items.append(data.items)
73+
74+
if len(items) == 1:
75+
inner_schema = items[0]
76+
else:
77+
inner_schema = oai.Schema(anyOf=items)
78+
6379
inner_prop, schemas = property_from_data(
6480
name=f"{name}_item",
6581
required=True,
66-
data=data.items,
82+
data=inner_schema,
6783
schemas=schemas,
6884
parent_name=parent_name,
6985
config=config,

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

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Schema(BaseModel):
4343
anyOf: List[Union[Reference, "Schema"]] = Field(default_factory=list)
4444
schema_not: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="not")
4545
items: Optional[Union[Reference, "Schema"]] = None
46+
prefixItems: Optional[List[Union[Reference, "Schema"]]] = Field(default_factory=list)
4647
properties: Optional[Dict[str, Union[Reference, "Schema"]]] = None
4748
additionalProperties: Optional[Union[bool, Reference, "Schema"]] = None
4849
description: Optional[str] = None

0 commit comments

Comments
 (0)