Skip to content

Support inline object schemas #236

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 33 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
de20cff
Refactor model building, add e2e section for inline objects.
dbanty Oct 3, 2020
3d4d7fb
Merge branch 'main' into support_inline_object_schemas
dbanty Oct 7, 2020
890ef7e
Merge branch 'main' into support_inline_object_schemas
dbanty Oct 14, 2020
1150b37
[WIP] Major refactor of models/enums/schemas.
dbanty Oct 14, 2020
be09dad
Added support for UNSET values and better differentiation between req…
emann Nov 2, 2020
921f235
Fixed typing issues in golden record and added some test endpoints
emann Nov 2, 2020
ae9e2b3
Brought test coverage back to what it was before
emann Nov 2, 2020
957ce46
Updated Changelog
emann Nov 2, 2020
fb4cb6f
Apply suggestions from code review
emann Nov 3, 2020
8f7d51b
Removed to_dict params and cleaned up type strings
emann Nov 3, 2020
961f6df
Fixed typing issues (again)
emann Nov 4, 2020
8b9b3d8
Merge branch 'main' into support_inline_object_schemas
dbanty Nov 4, 2020
eb1c52e
Merge branch 'feature/add-unset-value-and-model-to-dict-params' into …
dbanty Nov 4, 2020
0ab3ddf
Apply suggestions from code review
emann Nov 6, 2020
9e9ebbe
Cleaned up union type strings when property is not required + require…
emann Nov 6, 2020
6bc9542
Merge branch 'main' of https://github.com/triaxtec/openapi-python-cli…
emann Nov 6, 2020
a669ba6
Regenerated the golden record
emann Nov 6, 2020
b6ea790
Manually fix a bunch of tests after merging main and feature/unset in…
dbanty Nov 6, 2020
0fb3c48
Merge branch 'main' into support_inline_object_schemas
dbanty Nov 6, 2020
745a7a3
Merge branch 'feature/add-unset-value-and-model-to-dict-params' into …
dbanty Nov 6, 2020
5addb4a
Fix naming and required/not required for shared enums/models
dbanty Nov 6, 2020
66fa9a8
Merge branch 'main' into support_inline_object_schemas
dbanty Nov 6, 2020
dc6971d
Switch properties to use attr.s instead of dataclass
dbanty Nov 6, 2020
4cc04b3
Fix forward references and properly allow Unset for ModelPropertys
dbanty Nov 6, 2020
9bc20ed
Fix required properties in generated models
dbanty Nov 7, 2020
c8ae42d
Fix custom template E2E test
dbanty Nov 7, 2020
4743ae8
Refactored response handling to use the same schema generation as inp…
dbanty Nov 7, 2020
f3672a1
Fix typing in custom template e2e test
dbanty Nov 7, 2020
5575eea
Remove slots from attr classes to support Python 3.6
dbanty Nov 8, 2020
fbdbd21
Added improved naming scheme using parent elements
dbanty Nov 8, 2020
f17f9d0
Unit test coverage for new property/response code
dbanty Nov 8, 2020
45da271
Fix duplicate entry in CHANGELOG
dbanty Nov 8, 2020
c01a89f
Add note in CHANGELOG about `File` response
dbanty Nov 9, 2020
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ test-reports/
htmlcov/

# Generated end to end test data
my-test-api-client
my-test-api-client/
custom-e2e/
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Any request/response field that is not `required` and wasn't specified is now set to `UNSET` instead of `None`.
- Values that are `UNSET` will not be sent along in API calls
- Schemas defined with `type=object` will now be converted into classes, just like if they were created as ref components.
The previous behavior was a combination of skipping and using generic Dicts for these schemas.
- Response schema handling was unified with input schema handling, meaning that responses will behave differently than before.
Specifically, instead of the content-type deciding what the generated Python type is, the schema itself will.
- Instead of skipping input properties with no type, enum, anyOf, or oneOf declared, the property will be declared as `None`.
- Class (models and Enums) names will now contain the name of their parent element (if any). For example, a property
declared in an endpoint will be named like {endpoint_name}_{previous_class_name}. Classes will no longer be
deduplicated by appending a number to the end of the generated name, so if two names conflict with this new naming
scheme, there will be an error instead.

### Additions

- Added a `--custom-template-path` option for providing custom jinja2 templates (#231 - Thanks @erichulburd!).
- Better compatibility for "required" (whether or not the field must be included) and "nullable" (whether or not the field can be null) (#205 & #208). Thanks @bowenwr & @emannguitar!
- Support for all the same schemas in responses as are supported in parameters.

## 0.6.2 - 2020-11-03

Expand All @@ -26,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update minimum Pydantic version to support Python 3.9

### Additions
- Better compatibility for "required" (whether or not the field must be included) and "nullable" (whether or not the field can be null) (#205 & #208). Thanks @bowenwr & @emannguitar!

- Allow specifying the generated client's version using `package_version_override` in a config file. (#225 - Thanks @fyhertz!)

Expand Down
13 changes: 13 additions & 0 deletions end_to_end_tests/custom_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
project_name_override: "custom-e2e"
package_name_override: "custom_e2e"
class_overrides:
_ABCResponse:
class_name: ABCResponse
module_name: abc_response
AnEnumValueItem:
class_name: AnEnumValue
module_name: an_enum_value
NestedListOfEnumsItemItem:
class_name: AnEnumValue
module_name: an_enum_value
field_prefix: attr_
18 changes: 9 additions & 9 deletions end_to_end_tests/golden-record-custom/README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
# my-test-api-client
# custom-e2e
A client library for accessing My Test API

## Usage
First, create a client:

```python
from my_test_api_client import Client
from custom_e2e import Client

client = Client(base_url="https://api.example.com")
```

If the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead:

```python
from my_test_api_client import AuthenticatedClient
from custom_e2e import AuthenticatedClient

client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
```

Now call your endpoint and use your models:

```python
from my_test_api_client.models import MyDataModel
from my_test_api_client.api.my_tag import get_my_data_model
from custom_e2e.models import MyDataModel
from custom_e2e.api.my_tag import get_my_data_model

my_data: MyDataModel = get_my_data_model(client=client)
```

Or do the same thing with an async version:

```python
from my_test_api_client.models import MyDataModel
from my_test_api_client.async_api.my_tag import get_my_data_model
from custom_e2e.models import MyDataModel
from custom_e2e.async_api.my_tag import get_my_data_model

my_data: MyDataModel = await get_my_data_model(client=client)
```
Expand All @@ -40,9 +40,9 @@ Things to know:
1. Every path/method combo becomes a Python function with type annotations.
1. All path/query params, and bodies become method arguments.
1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above)
1. Any endpoint which did not have a tag will be in `my_test_api_client.api.default`
1. Any endpoint which did not have a tag will be in `custom_e2e.api.default`
1. If the API returns a response code that was not declared in the OpenAPI document, a
`my_test_api_client.api.errors.ApiResponseError` wil be raised
`custom_e2e.api.errors.ApiResponseError` wil be raised
with the `response` attribute set to the `httpx.Response` that was received.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import httpx

from ...types import Response

Client = httpx.Client

import datetime
from typing import Dict, List, Optional, Union, cast
from typing import Dict, List, Union

from dateutil.parser import isoparse

Expand All @@ -16,14 +18,18 @@

def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
if response.status_code == 200:
return None
response_200 = None

return response_200
if response.status_code == 422:
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
response_422 = HTTPValidationError.from_dict(response.json())

return response_422
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[Union[None, HTTPValidationError]]:
return httpx.Response(
def _build_response(*, response: httpx.Response) -> Response[Union[None, HTTPValidationError]]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
Expand All @@ -34,7 +40,6 @@ def _build_response(*, response: httpx.Response) -> httpx.Response[Union[None, H
def httpx_request(
*,
client: Client,
json_body: Dict[Any, Any],
string_prop: Union[Unset, str] = "the default string",
datetime_prop: Union[Unset, datetime.datetime] = isoparse("1010-10-10T00:00:00"),
date_prop: Union[Unset, datetime.date] = isoparse("1010-10-10").date(),
Expand All @@ -44,7 +49,7 @@ def httpx_request(
list_prop: Union[Unset, List[AnEnum]] = UNSET,
union_prop: Union[Unset, float, str] = "not a float",
enum_prop: Union[Unset, AnEnum] = UNSET,
) -> httpx.Response[Union[None, HTTPValidationError]]:
) -> Response[Union[None, HTTPValidationError]]:

json_datetime_prop: Union[Unset, str] = UNSET
if not isinstance(datetime_prop, Unset):
Expand Down Expand Up @@ -94,12 +99,9 @@ def httpx_request(
if enum_prop is not UNSET:
params["enum_prop"] = json_enum_prop

json_json_body = json_body

response = client.request(
"post",
"/tests/defaults",
json=json_json_body,
params=params,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@

import httpx

from ...types import Response

Client = httpx.Client

from typing import List, cast


def _parse_response(*, response: httpx.Response) -> Optional[List[bool]]:
if response.status_code == 200:
return [bool(item) for item in cast(List[bool], response.json())]
response_200 = cast(List[bool], response.json())

return response_200
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[bool]]:
return httpx.Response(
def _build_response(*, response: httpx.Response) -> Response[List[bool]]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
Expand All @@ -23,7 +29,7 @@ def _build_response(*, response: httpx.Response) -> httpx.Response[List[bool]]:
def httpx_request(
*,
client: Client,
) -> httpx.Response[List[bool]]:
) -> Response[List[bool]]:

response = client.request(
"get",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@

import httpx

from ...types import Response

Client = httpx.Client

from typing import List, cast


def _parse_response(*, response: httpx.Response) -> Optional[List[float]]:
if response.status_code == 200:
return [float(item) for item in cast(List[float], response.json())]
response_200 = cast(List[float], response.json())

return response_200
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[float]]:
return httpx.Response(
def _build_response(*, response: httpx.Response) -> Response[List[float]]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
Expand All @@ -23,7 +29,7 @@ def _build_response(*, response: httpx.Response) -> httpx.Response[List[float]]:
def httpx_request(
*,
client: Client,
) -> httpx.Response[List[float]]:
) -> Response[List[float]]:

response = client.request(
"get",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@

import httpx

from ...types import Response

Client = httpx.Client

from typing import List, cast


def _parse_response(*, response: httpx.Response) -> Optional[List[int]]:
if response.status_code == 200:
return [int(item) for item in cast(List[int], response.json())]
response_200 = cast(List[int], response.json())

return response_200
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[int]]:
return httpx.Response(
def _build_response(*, response: httpx.Response) -> Response[List[int]]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
Expand All @@ -23,7 +29,7 @@ def _build_response(*, response: httpx.Response) -> httpx.Response[List[int]]:
def httpx_request(
*,
client: Client,
) -> httpx.Response[List[int]]:
) -> Response[List[int]]:

response = client.request(
"get",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@

import httpx

from ...types import Response

Client = httpx.Client

from typing import List, cast


def _parse_response(*, response: httpx.Response) -> Optional[List[str]]:
if response.status_code == 200:
return [str(item) for item in cast(List[str], response.json())]
response_200 = cast(List[str], response.json())

return response_200
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[str]]:
return httpx.Response(
def _build_response(*, response: httpx.Response) -> Response[List[str]]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
Expand All @@ -23,7 +29,7 @@ def _build_response(*, response: httpx.Response) -> httpx.Response[List[str]]:
def httpx_request(
*,
client: Client,
) -> httpx.Response[List[str]]:
) -> Response[List[str]]:

response = client.request(
"get",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import httpx

from ...types import Response

Client = httpx.Client

import datetime
from typing import Dict, List, Union, cast
from typing import Dict, List, Union

from ...models.a_model import AModel
from ...models.an_enum import AnEnum
Expand All @@ -14,14 +16,22 @@

def _parse_response(*, response: httpx.Response) -> Optional[Union[List[AModel], HTTPValidationError]]:
if response.status_code == 200:
return [AModel.from_dict(item) for item in cast(List[Dict[str, Any]], response.json())]
response_200 = []
for response_200_item_data in response.json():
response_200_item = AModel.from_dict(response_200_item_data)

response_200.append(response_200_item)

return response_200
if response.status_code == 422:
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
response_422 = HTTPValidationError.from_dict(response.json())

return response_422
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[Union[List[AModel], HTTPValidationError]]:
return httpx.Response(
def _build_response(*, response: httpx.Response) -> Response[Union[List[AModel], HTTPValidationError]]:
return Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
Expand All @@ -34,7 +44,7 @@ def httpx_request(
client: Client,
an_enum_value: List[AnEnum],
some_date: Union[datetime.date, datetime.datetime],
) -> httpx.Response[Union[List[AModel], HTTPValidationError]]:
) -> Response[Union[List[AModel], HTTPValidationError]]:

json_an_enum_value = []
for an_enum_value_item_data in an_enum_value:
Expand Down
Loading