Skip to content

Support inlined form data schema in requestBody #662

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

Merged
merged 6 commits into from
Aug 27, 2022
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
no_response_tests_no_response_get,
octet_stream_tests_octet_stream_get,
post_form_data,
post_form_data_inline,
post_tests_json_body_string,
test_inline_objects,
token_with_cookie_auth_token_with_cookie_get,
Expand Down Expand Up @@ -66,6 +67,13 @@ def post_form_data(cls) -> types.ModuleType:
"""
return post_form_data

@classmethod
def post_form_data_inline(cls) -> types.ModuleType:
"""
Post form data (inline schema)
"""
return post_form_data_inline

@classmethod
def upload_file_tests_upload_post(cls) -> types.ModuleType:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def sync_detailed(
client: Client,
form_data: AFormData,
) -> Response[Any]:
"""Post from data
"""Post form data

Post form data

Expand All @@ -67,7 +67,7 @@ async def asyncio_detailed(
client: Client,
form_data: AFormData,
) -> Response[Any]:
"""Post from data
"""Post form data

Post form data

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from typing import Any, Dict

import httpx

from ...client import Client
from ...models.post_form_data_inline_data import PostFormDataInlineData
from ...types import Response


def _get_kwargs(
*,
client: Client,
form_data: PostFormDataInlineData,
) -> Dict[str, Any]:
url = "{}/tests/post_form_data_inline".format(client.base_url)

headers: Dict[str, str] = client.get_headers()
cookies: Dict[str, Any] = client.get_cookies()

return {
"method": "post",
"url": url,
"headers": headers,
"cookies": cookies,
"timeout": client.get_timeout(),
"data": form_data.to_dict(),
}


def _build_response(*, response: httpx.Response) -> Response[Any]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=None,
)


def sync_detailed(
*,
client: Client,
form_data: PostFormDataInlineData,
) -> Response[Any]:
"""Post form data (inline schema)

Post form data (inline schema)

Returns:
Response[Any]
"""

kwargs = _get_kwargs(
client=client,
form_data=form_data,
)

response = httpx.request(
verify=client.verify_ssl,
**kwargs,
)

return _build_response(response=response)


async def asyncio_detailed(
*,
client: Client,
form_data: PostFormDataInlineData,
) -> Response[Any]:
"""Post form data (inline schema)

Post form data (inline schema)

Returns:
Response[Any]
"""

kwargs = _get_kwargs(
client=client,
form_data=form_data,
)

async with httpx.AsyncClient(verify=client.verify_ssl) as _client:
response = await _client.request(**kwargs)

return _build_response(response=response)
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from .model_with_union_property_inlined_fruit_type_0 import ModelWithUnionPropertyInlinedFruitType0
from .model_with_union_property_inlined_fruit_type_1 import ModelWithUnionPropertyInlinedFruitType1
from .none import None_
from .post_form_data_inline_data import PostFormDataInlineData
from .post_responses_unions_simple_before_complex_response_200 import PostResponsesUnionsSimpleBeforeComplexResponse200
from .post_responses_unions_simple_before_complex_response_200a_type_1 import (
PostResponsesUnionsSimpleBeforeComplexResponse200AType1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Any, Dict, List, Type, TypeVar, Union

import attr

from ..types import UNSET, Unset

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


@attr.s(auto_attribs=True)
class PostFormDataInlineData:
"""
Attributes:
a_required_field (str):
an_optional_field (Union[Unset, str]):
"""

a_required_field: str
an_optional_field: Union[Unset, str] = UNSET
additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict)

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

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

return field_dict

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

an_optional_field = d.pop("an_optional_field", UNSET)

post_form_data_inline_data = cls(
a_required_field=a_required_field,
an_optional_field=an_optional_field,
)

post_form_data_inline_data.additional_properties = d
return post_form_data_inline_data

@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
43 changes: 42 additions & 1 deletion end_to_end_tests/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@
"tags": [
"tests"
],
"summary": "Post from data",
"summary": "Post form data",
"description": "Post form data",
"operationId": "post_form_data",
"requestBody": {
Expand All @@ -242,6 +242,47 @@
}
}
},
"/tests/post_form_data_inline": {
"post": {
"tags": [
"tests"
],
"summary": "Post form data (inline schema)",
"description": "Post form data (inline schema)",
"operationId": "post_form_data_inline",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"an_optional_field": {
"type": "string"
},
"a_required_field": {
"type": "string"
}
},
"required": [
"a_required_field"
]
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/tests/upload": {
"post": {
"tags": [
Expand Down
50 changes: 38 additions & 12 deletions openapi_python_client/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,32 @@ class Endpoint:
header_parameters: Dict[str, Property] = field(default_factory=dict)
cookie_parameters: Dict[str, Property] = field(default_factory=dict)
responses: List[Response] = field(default_factory=list)
form_body_class: Optional[Class] = None
form_body: Optional[Property] = None
json_body: Optional[Property] = None
multipart_body: Optional[Property] = None
errors: List[ParseError] = field(default_factory=list)
used_python_identifiers: Set[PythonIdentifier] = field(default_factory=set)

@staticmethod
def parse_request_form_body(*, body: oai.RequestBody, config: Config) -> Optional[Class]:
"""Return form_body_reference"""
def parse_request_form_body(
*, body: oai.RequestBody, schemas: Schemas, parent_name: str, config: Config
) -> Tuple[Union[Property, PropertyError, None], Schemas]:
"""Return form_body and updated schemas"""
body_content = body.content
form_body = body_content.get("application/x-www-form-urlencoded")
if form_body is not None and isinstance(form_body.media_type_schema, oai.Reference):
return Class.from_string(string=form_body.media_type_schema.ref, config=config)
return None
if form_body is not None and form_body.media_type_schema is not None:
prop, schemas = property_from_data(
name="data",
required=True,
data=form_body.media_type_schema,
schemas=schemas,
parent_name=parent_name,
config=config,
)
if isinstance(prop, ModelProperty):
schemas = attr.evolve(schemas, classes_by_name={**schemas.classes_by_name, prop.class_info.name: prop})
return prop, schemas
return None, schemas

@staticmethod
def parse_multipart_body(
Expand Down Expand Up @@ -186,7 +198,20 @@ def _add_body(
if data.requestBody is None or isinstance(data.requestBody, oai.Reference):
return endpoint, schemas

endpoint.form_body_class = Endpoint.parse_request_form_body(body=data.requestBody, config=config)
form_body, schemas = Endpoint.parse_request_form_body(
body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config
)

if isinstance(form_body, ParseError):
return (
ParseError(
header=f"Cannot parse form body of endpoint {endpoint.name}",
detail=form_body.detail,
data=form_body.data,
),
schemas,
)

json_body, schemas = Endpoint.parse_request_json_body(
body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config
)
Expand All @@ -213,8 +238,9 @@ def _add_body(
schemas,
)

if endpoint.form_body_class:
endpoint.relative_imports.add(import_string_from_class(endpoint.form_body_class, prefix="...models"))
if form_body is not None:
endpoint.form_body = form_body
endpoint.relative_imports.update(endpoint.form_body.get_imports(prefix="..."))
if multipart_body is not None:
endpoint.multipart_body = multipart_body
endpoint.relative_imports.update(endpoint.multipart_body.get_imports(prefix="..."))
Expand Down Expand Up @@ -285,9 +311,9 @@ def add_parameters(
config: User-provided config for overrides within parameters.

Returns:
`(result, schemas)` where `result` is either an updated Endpoint containing the parameters or a ParseError
describing what went wrong. `schemas` is an updated version of the `schemas` input, adding any new enums
or classes.
`(result, schemas, parameters)` where `result` is either an updated Endpoint containing the parameters or a
ParseError describing what went wrong. `schemas` is an updated version of the `schemas` input, adding any
new enums or classes. `parameters` is an updated version of the `parameters` input, adding new parameters.

See Also:
- https://swagger.io/docs/specification/describing-parameters/
Expand Down
6 changes: 3 additions & 3 deletions openapi_python_client/templates/endpoint_macros.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ client: AuthenticatedClient,
client: Client,
{% endif %}
{# Form data if any #}
{% if endpoint.form_body_class %}
form_data: {{ endpoint.form_body_class.name }},
{% if endpoint.form_body %}
form_data: {{ endpoint.form_body.get_type_string() }},
{% endif %}
{# Multipart data if any #}
{% if endpoint.multipart_body %}
Expand Down Expand Up @@ -120,7 +120,7 @@ json_body: {{ endpoint.json_body.get_type_string() }},
{{ parameter.python_name }}={{ parameter.python_name }},
{% endfor %}
client=client,
{% if endpoint.form_body_class %}
{% if endpoint.form_body %}
form_data=form_data,
{% endif %}
{% if endpoint.multipart_body %}
Expand Down
2 changes: 1 addition & 1 deletion openapi_python_client/templates/endpoint_module.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _get_kwargs(
"headers": headers,
"cookies": cookies,
"timeout": client.get_timeout(),
{% if endpoint.form_body_class %}
{% if endpoint.form_body %}
"data": form_data.to_dict(),
{% elif endpoint.multipart_body %}
"files": {{ "multipart_" + endpoint.multipart_body.python_name }},
Expand Down
Loading