Skip to content

Resolve circular import for Operation and PathItem #661

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 3 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 @@ -3,6 +3,7 @@
import types

from . import (
callback_test,
defaults_tests_defaults_post,
get_basic_list_of_booleans,
get_basic_list_of_floats,
Expand Down Expand Up @@ -150,3 +151,10 @@ def token_with_cookie_auth_token_with_cookie_get(cls) -> types.ModuleType:
Test optional cookie parameters
"""
return token_with_cookie_auth_token_with_cookie_get

@classmethod
def callback_test(cls) -> types.ModuleType:
"""
Try sending a request related to a callback
"""
return callback_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
from typing import Any, Dict, Optional, Union, cast

import httpx

from ...client import Client
from ...models.a_model import AModel
from ...models.http_validation_error import HTTPValidationError
from ...types import Response


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

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

json_json_body = json_body.to_dict()

return {
"method": "post",
"url": url,
"headers": headers,
"cookies": cookies,
"timeout": client.get_timeout(),
"json": json_json_body,
}


def _parse_response(*, response: httpx.Response) -> Optional[Union[Any, HTTPValidationError]]:
if response.status_code == 200:
response_200 = cast(Any, response.json())
return response_200
if response.status_code == 422:
response_422 = HTTPValidationError.from_dict(response.json())

return response_422
return None


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


def sync_detailed(
*,
client: Client,
json_body: AModel,
) -> Response[Union[Any, HTTPValidationError]]:
"""Path with callback

Try sending a request related to a callback

Args:
json_body (AModel): A Model for testing all the ways custom objects can be used

Returns:
Response[Union[Any, HTTPValidationError]]
"""

kwargs = _get_kwargs(
client=client,
json_body=json_body,
)

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

return _build_response(response=response)


def sync(
*,
client: Client,
json_body: AModel,
) -> Optional[Union[Any, HTTPValidationError]]:
"""Path with callback

Try sending a request related to a callback

Args:
json_body (AModel): A Model for testing all the ways custom objects can be used

Returns:
Response[Union[Any, HTTPValidationError]]
"""

return sync_detailed(
client=client,
json_body=json_body,
).parsed


async def asyncio_detailed(
*,
client: Client,
json_body: AModel,
) -> Response[Union[Any, HTTPValidationError]]:
"""Path with callback

Try sending a request related to a callback

Args:
json_body (AModel): A Model for testing all the ways custom objects can be used

Returns:
Response[Union[Any, HTTPValidationError]]
"""

kwargs = _get_kwargs(
client=client,
json_body=json_body,
)

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

return _build_response(response=response)


async def asyncio(
*,
client: Client,
json_body: AModel,
) -> Optional[Union[Any, HTTPValidationError]]:
"""Path with callback

Try sending a request related to a callback

Args:
json_body (AModel): A Model for testing all the ways custom objects can be used

Returns:
Response[Union[Any, HTTPValidationError]]
"""

return (
await asyncio_detailed(
client=client,
json_body=json_body,
)
).parsed
56 changes: 56 additions & 0 deletions end_to_end_tests/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,62 @@
}
}
}
},
"/tests/callback": {
"post": {
"tags": [
"tests"
],
"summary": "Path with callback",
"description": "Try sending a request related to a callback",
"operationId": "callback_test",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AModel"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
},
"callbacks": {
"event": {
"callback": {
"post": {
"responses": {
"200": {
"description": "Success"
},
"503": {
"description": "Unavailable"
}
}
}
}
}
}
}
}
},
"components": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from .callback import Callback
from .external_documentation import ExternalDocumentation
from .parameter import Parameter

# Required to update forward ref after object creation, as this is not imported yet
from .path_item import PathItem # pylint: disable=unused-import
from .reference import Reference
from .request_body import RequestBody
from .responses import Responses
Expand Down Expand Up @@ -79,3 +82,7 @@ class Config: # pylint: disable=missing-class-docstring
}
]
}


# PathItem in Callback uses Operation, so we need to update forward refs due to circular dependency
Operation.update_forward_refs()
23 changes: 14 additions & 9 deletions openapi_python_client/schema/openapi_schema_pydantic/path_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from pydantic import BaseModel, Extra, Field

from .operation import Operation
from .parameter import Parameter
from .reference import Reference
from .server import Server
Expand All @@ -23,14 +22,14 @@ class PathItem(BaseModel):
ref: Optional[str] = Field(default=None, alias="$ref")
summary: Optional[str] = None
description: Optional[str] = None
get: Optional[Operation] = None
put: Optional[Operation] = None
post: Optional[Operation] = None
delete: Optional[Operation] = None
options: Optional[Operation] = None
head: Optional[Operation] = None
patch: Optional[Operation] = None
trace: Optional[Operation] = None
get: Optional["Operation"] = None
put: Optional["Operation"] = None
post: Optional["Operation"] = None
delete: Optional["Operation"] = None
options: Optional["Operation"] = None
head: Optional["Operation"] = None
patch: Optional["Operation"] = None
trace: Optional["Operation"] = None
servers: Optional[List[Server]] = None
parameters: Optional[List[Union[Parameter, Reference]]] = None

Expand Down Expand Up @@ -70,3 +69,9 @@ class Config: # pylint: disable=missing-class-docstring
}
]
}


# Operation uses PathItem via Callback, so we need late import and to update forward refs due to circular dependency
from .operation import Operation # pylint: disable=wrong-import-position unused-import

PathItem.update_forward_refs()
20 changes: 20 additions & 0 deletions tests/test_schema/test_open_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,23 @@ def test_validate_version(version, valid):
else:
with pytest.raises(ValidationError):
OpenAPI.parse_obj(data)


def test_parse_with_callback():
data = {
"openapi": "3.0.1",
"info": {"title": "API with Callback", "version": ""},
"paths": {
"/create": {
"post": {
"responses": {"200": {"description": "Success"}},
"callbacks": {"event": {"callback": {"post": {"responses": {"200": {"description": "Success"}}}}}},
}
}
},
}

open_api = OpenAPI.parse_obj(data)
create_endpoint = open_api.paths["/create"]
assert "200" in create_endpoint.post.responses
assert "200" in create_endpoint.post.callbacks["event"]["callback"].post.responses