Skip to content
Open
Show file tree
Hide file tree
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
11 changes: 11 additions & 0 deletions .changeset/fix_unset_import_for_required_multi_body_endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
default: patch
---

# Unset used but not imported for required multi-body endpoints

#1478 by @rolandgeider

Closes #1451

We ran into this while generating a client from our spec. The multi-body branch of the `arguments` macro read `body_required` before it was ever assigned, so `| Unset = UNSET` was appended to the `body` annotation even for a required request body.
54 changes: 54 additions & 0 deletions end_to_end_tests/baseline_openapi_3.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,60 @@
}
}
},
"/bodies/multiple-required": {
"post": {
"description": "Test multiple bodies, all required",
"tags": [
"bodies"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
},
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/bodies/json-like": {
"post": {
"tags": [
Expand Down
54 changes: 54 additions & 0 deletions end_to_end_tests/baseline_openapi_3.1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,60 @@ info:
}
}
},
"/bodies/multiple-required": {
"post": {
"description": "Test multiple bodies, all required",
"tags": [
"bodies"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
},
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/bodies/json-like": {
"post": {
"tags": [ "bodies" ],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import types

from . import json_like, optional_body, post_bodies_multiple, refs
from . import json_like, optional_body, post_bodies_multiple, post_bodies_multiple_required, refs


class BodiesEndpoints:
Expand All @@ -13,6 +13,13 @@ def post_bodies_multiple(cls) -> types.ModuleType:
"""
return post_bodies_multiple

@classmethod
def post_bodies_multiple_required(cls) -> types.ModuleType:
"""
Test multiple bodies, all required
"""
return post_bodies_multiple_required

@classmethod
def json_like(cls) -> types.ModuleType:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from http import HTTPStatus
from typing import Any

import httpx

from ... import errors
from ...client import AuthenticatedClient, Client
from ...models.post_bodies_multiple_required_data_body import PostBodiesMultipleRequiredDataBody
from ...models.post_bodies_multiple_required_files_body import PostBodiesMultipleRequiredFilesBody
from ...models.post_bodies_multiple_required_json_body import PostBodiesMultipleRequiredJsonBody
from ...types import File, Response


def _get_kwargs(
*,
body: PostBodiesMultipleRequiredJsonBody
| File
| PostBodiesMultipleRequiredDataBody
| PostBodiesMultipleRequiredFilesBody,
) -> dict[str, Any]:
headers: dict[str, Any] = {}

_kwargs: dict[str, Any] = {
"method": "post",
"url": "/bodies/multiple-required",
}

if isinstance(body, PostBodiesMultipleRequiredJsonBody):
_kwargs["json"] = body.to_dict()

headers["Content-Type"] = "application/json"
if isinstance(body, File):
_kwargs["content"] = body.payload
headers["Content-Type"] = "application/octet-stream"
if isinstance(body, PostBodiesMultipleRequiredDataBody):
_kwargs["data"] = body.to_dict()
headers["Content-Type"] = "application/x-www-form-urlencoded"
if isinstance(body, PostBodiesMultipleRequiredFilesBody):
_kwargs["files"] = body.to_multipart()

headers["Content-Type"] = "multipart/form-data; boundary=+++"

_kwargs["headers"] = headers
return _kwargs


def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Any | None:
if response.status_code == 200:
return None

if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
return None


def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Any]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
headers=response.headers,
parsed=_parse_response(client=client, response=response),
)


def sync_detailed(
*,
client: AuthenticatedClient | Client,
body: PostBodiesMultipleRequiredJsonBody
| File
| PostBodiesMultipleRequiredDataBody
| PostBodiesMultipleRequiredFilesBody,
) -> Response[Any]:
"""Test multiple bodies, all required

Args:
body (PostBodiesMultipleRequiredJsonBody):
body (File):
body (PostBodiesMultipleRequiredDataBody):
body (PostBodiesMultipleRequiredFilesBody):

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Response[Any]
"""

kwargs = _get_kwargs(
body=body,
)

response = client.get_httpx_client().request(
**kwargs,
)

return _build_response(client=client, response=response)


async def asyncio_detailed(
*,
client: AuthenticatedClient | Client,
body: PostBodiesMultipleRequiredJsonBody
| File
| PostBodiesMultipleRequiredDataBody
| PostBodiesMultipleRequiredFilesBody,
) -> Response[Any]:
"""Test multiple bodies, all required

Args:
body (PostBodiesMultipleRequiredJsonBody):
body (File):
body (PostBodiesMultipleRequiredDataBody):
body (PostBodiesMultipleRequiredFilesBody):

Raises:
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
httpx.TimeoutException: If the request takes longer than Client.timeout.

Returns:
Response[Any]
"""

kwargs = _get_kwargs(
body=body,
)

response = await client.get_async_httpx_client().request(**kwargs)

return _build_response(client=client, response=response)
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
from .post_bodies_multiple_data_body import PostBodiesMultipleDataBody
from .post_bodies_multiple_files_body import PostBodiesMultipleFilesBody
from .post_bodies_multiple_json_body import PostBodiesMultipleJsonBody
from .post_bodies_multiple_required_data_body import PostBodiesMultipleRequiredDataBody
from .post_bodies_multiple_required_files_body import PostBodiesMultipleRequiredFilesBody
from .post_bodies_multiple_required_json_body import PostBodiesMultipleRequiredJsonBody
from .post_form_data_inline_body import PostFormDataInlineBody
from .post_naming_property_conflict_with_import_body import PostNamingPropertyConflictWithImportBody
from .post_naming_property_conflict_with_import_response_200 import PostNamingPropertyConflictWithImportResponse200
Expand Down Expand Up @@ -171,6 +174,9 @@
"PostBodiesMultipleDataBody",
"PostBodiesMultipleFilesBody",
"PostBodiesMultipleJsonBody",
"PostBodiesMultipleRequiredDataBody",
"PostBodiesMultipleRequiredFilesBody",
"PostBodiesMultipleRequiredJsonBody",
"PostFormDataInlineBody",
"PostNamingPropertyConflictWithImportBody",
"PostNamingPropertyConflictWithImportResponse200",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any, TypeVar

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

from ..types import UNSET, Unset

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


@_attrs_define
class PostBodiesMultipleRequiredDataBody:
"""
Attributes:
a (str | Unset):
"""

a: str | Unset = UNSET
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)

def to_dict(self) -> dict[str, Any]:
a = self.a

field_dict: dict[str, Any] = {}
field_dict.update(self.additional_properties)
field_dict.update({})
if a is not UNSET:
field_dict["a"] = a

return field_dict

@classmethod
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
d = dict(src_dict)
a = d.pop("a", UNSET)

post_bodies_multiple_required_data_body = cls(
a=a,
)

post_bodies_multiple_required_data_body.additional_properties = d
return post_bodies_multiple_required_data_body

@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
Loading