[python-fastapi] return 500 if not implemented, added some unittests (#19196)

* [python-fastapi] Added some tests for FastAPI generator

1. Checks the generation of the implementation package.
2. Checks if the endpoints with and without descriptions generate correct
   output.

Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>

* [python-fastapi] Raise 500 if there is no implementation

Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>

---------

Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
This commit is contained in:
Nikita Vakula 2024-07-19 10:14:16 +02:00 committed by GitHub
parent f44bc30d20
commit e542b06869
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 141 additions and 1 deletions

View File

@ -14,6 +14,7 @@ from fastapi import ( # noqa: F401
Depends, Depends,
Form, Form,
Header, Header,
HTTPException,
Path, Path,
Query, Query,
Response, Response,
@ -65,7 +66,9 @@ async def {{operationId}}(
{{/hasAuthMethods}} {{/hasAuthMethods}}
) -> {{returnType}}{{^returnType}}None{{/returnType}}: ) -> {{returnType}}{{^returnType}}None{{/returnType}}:
{{#notes}}"""{{.}}""" {{#notes}}"""{{.}}"""
{{/notes}}return await Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}}) {{/notes}}if not Base{{classname}}.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await Base{{classname}}.subclasses[0]().{{operationId}}({{#allParams}}{{>impl_argument}}{{^-last}}, {{/-last}}{{/allParams}})
{{^-last}} {{^-last}}

View File

@ -0,0 +1,56 @@
package org.openapitools.codegen.python;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.testng.annotations.Test;
import org.openapitools.codegen.CodegenConstants;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class PythonFastapiCodegenTest {
@Test
public void testAdditionalPropertiesPutForConfigValues() throws Exception {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final String IMPL_PKG = "impl_package";
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("python-fastapi")
.setPackageName("nodesc")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"))
.setInputSpec("src/test/resources/3_1/nodesc.yaml")
.addAdditionalProperty(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, IMPL_PKG);
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
TestUtils.assertFileExists(Paths.get(output.getAbsolutePath(), "/src", IMPL_PKG, "__init__.py"));
}
@Test
public void testEndpointSpecsWithoutDescription() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("python-fastapi")
.setPackageName("nodesc")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"))
.setInputSpec("src/test/resources/3_1/nodesc.yaml");
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
TestUtils.assertFileContains(Paths.get(output + "/src/nodesc/apis/nodesc_api.py"),
"return await BaseNodescApi.subclasses[0]().nodesc()\n");
TestUtils.assertFileContains(Paths.get(output + "/src/nodesc/apis/desc_api.py"),
"return await BaseDescApi.subclasses[0]().desc()\n");
}
}

View File

@ -0,0 +1,35 @@
openapi: 3.1.0
info:
description: >-
Yet another example.
version: 1.0.0
title: OpenAPI example
tags:
- name: nodesc
description: Endpoints have no description
- name: desc
description: Endpoints have description
paths:
/nodesc:
post:
tags:
- nodesc
summary: dummy operation
operationId: nodesc
responses:
'200':
description: successful operation
'405':
description: Invalid input
/desc:
post:
tags:
- desc
summary: dummy operation
description: 'Description'
operationId: desc
responses:
'200':
description: successful operation
'405':
description: Invalid input

View File

@ -14,6 +14,7 @@ from fastapi import ( # noqa: F401
Depends, Depends,
Form, Form,
Header, Header,
HTTPException,
Path, Path,
Query, Query,
Response, Response,
@ -46,4 +47,6 @@ async def fake_query_param_default(
no_default: str = Query(None, description="no default value", alias="noDefault"), no_default: str = Query(None, description="no default value", alias="noDefault"),
) -> None: ) -> None:
"""""" """"""
if not BaseFakeApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseFakeApi.subclasses[0]().fake_query_param_default(has_default, no_default) return await BaseFakeApi.subclasses[0]().fake_query_param_default(has_default, no_default)

View File

@ -14,6 +14,7 @@ from fastapi import ( # noqa: F401
Depends, Depends,
Form, Form,
Header, Header,
HTTPException,
Path, Path,
Query, Query,
Response, Response,
@ -50,6 +51,8 @@ async def add_pet(
), ),
) -> Pet: ) -> Pet:
"""""" """"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().add_pet(pet) return await BasePetApi.subclasses[0]().add_pet(pet)
@ -70,6 +73,8 @@ async def delete_pet(
), ),
) -> None: ) -> None:
"""""" """"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().delete_pet(petId, api_key) return await BasePetApi.subclasses[0]().delete_pet(petId, api_key)
@ -90,6 +95,8 @@ async def find_pets_by_status(
), ),
) -> List[Pet]: ) -> List[Pet]:
"""Multiple status values can be provided with comma separated strings""" """Multiple status values can be provided with comma separated strings"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().find_pets_by_status(status) return await BasePetApi.subclasses[0]().find_pets_by_status(status)
@ -110,6 +117,8 @@ async def find_pets_by_tags(
), ),
) -> List[Pet]: ) -> List[Pet]:
"""Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.""" """Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing."""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().find_pets_by_tags(tags) return await BasePetApi.subclasses[0]().find_pets_by_tags(tags)
@ -131,6 +140,8 @@ async def get_pet_by_id(
), ),
) -> Pet: ) -> Pet:
"""Returns a single pet""" """Returns a single pet"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().get_pet_by_id(petId) return await BasePetApi.subclasses[0]().get_pet_by_id(petId)
@ -153,6 +164,8 @@ async def update_pet(
), ),
) -> Pet: ) -> Pet:
"""""" """"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().update_pet(pet) return await BasePetApi.subclasses[0]().update_pet(pet)
@ -174,6 +187,8 @@ async def update_pet_with_form(
), ),
) -> None: ) -> None:
"""""" """"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().update_pet_with_form(petId, name, status) return await BasePetApi.subclasses[0]().update_pet_with_form(petId, name, status)
@ -195,4 +210,6 @@ async def upload_file(
), ),
) -> ApiResponse: ) -> ApiResponse:
"""""" """"""
if not BasePetApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BasePetApi.subclasses[0]().upload_file(petId, additional_metadata, file) return await BasePetApi.subclasses[0]().upload_file(petId, additional_metadata, file)

View File

@ -14,6 +14,7 @@ from fastapi import ( # noqa: F401
Depends, Depends,
Form, Form,
Header, Header,
HTTPException,
Path, Path,
Query, Query,
Response, Response,
@ -46,6 +47,8 @@ async def delete_order(
orderId: str = Path(..., description="ID of the order that needs to be deleted"), orderId: str = Path(..., description="ID of the order that needs to be deleted"),
) -> None: ) -> None:
"""For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors""" """For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors"""
if not BaseStoreApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseStoreApi.subclasses[0]().delete_order(orderId) return await BaseStoreApi.subclasses[0]().delete_order(orderId)
@ -64,6 +67,8 @@ async def get_inventory(
), ),
) -> Dict[str, int]: ) -> Dict[str, int]:
"""Returns a map of status codes to quantities""" """Returns a map of status codes to quantities"""
if not BaseStoreApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseStoreApi.subclasses[0]().get_inventory() return await BaseStoreApi.subclasses[0]().get_inventory()
@ -82,6 +87,8 @@ async def get_order_by_id(
orderId: int = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5), orderId: int = Path(..., description="ID of pet that needs to be fetched", ge=1, le=5),
) -> Order: ) -> Order:
"""For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generate exceptions""" """For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generate exceptions"""
if not BaseStoreApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseStoreApi.subclasses[0]().get_order_by_id(orderId) return await BaseStoreApi.subclasses[0]().get_order_by_id(orderId)
@ -99,4 +106,6 @@ async def place_order(
order: Order = Body(None, description="order placed for purchasing the pet"), order: Order = Body(None, description="order placed for purchasing the pet"),
) -> Order: ) -> Order:
"""""" """"""
if not BaseStoreApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseStoreApi.subclasses[0]().place_order(order) return await BaseStoreApi.subclasses[0]().place_order(order)

View File

@ -14,6 +14,7 @@ from fastapi import ( # noqa: F401
Depends, Depends,
Form, Form,
Header, Header,
HTTPException,
Path, Path,
Query, Query,
Response, Response,
@ -48,6 +49,8 @@ async def create_user(
), ),
) -> None: ) -> None:
"""This can only be done by the logged in user.""" """This can only be done by the logged in user."""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().create_user(user) return await BaseUserApi.subclasses[0]().create_user(user)
@ -67,6 +70,8 @@ async def create_users_with_array_input(
), ),
) -> None: ) -> None:
"""""" """"""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().create_users_with_array_input(user) return await BaseUserApi.subclasses[0]().create_users_with_array_input(user)
@ -86,6 +91,8 @@ async def create_users_with_list_input(
), ),
) -> None: ) -> None:
"""""" """"""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().create_users_with_list_input(user) return await BaseUserApi.subclasses[0]().create_users_with_list_input(user)
@ -106,6 +113,8 @@ async def delete_user(
), ),
) -> None: ) -> None:
"""This can only be done by the logged in user.""" """This can only be done by the logged in user."""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().delete_user(username) return await BaseUserApi.subclasses[0]().delete_user(username)
@ -124,6 +133,8 @@ async def get_user_by_name(
username: str = Path(..., description="The name that needs to be fetched. Use user1 for testing."), username: str = Path(..., description="The name that needs to be fetched. Use user1 for testing."),
) -> User: ) -> User:
"""""" """"""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().get_user_by_name(username) return await BaseUserApi.subclasses[0]().get_user_by_name(username)
@ -142,6 +153,8 @@ async def login_user(
password: str = Query(None, description="The password for login in clear text", alias="password"), password: str = Query(None, description="The password for login in clear text", alias="password"),
) -> str: ) -> str:
"""""" """"""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().login_user(username, password) return await BaseUserApi.subclasses[0]().login_user(username, password)
@ -160,6 +173,8 @@ async def logout_user(
), ),
) -> None: ) -> None:
"""""" """"""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().logout_user() return await BaseUserApi.subclasses[0]().logout_user()
@ -181,4 +196,6 @@ async def update_user(
), ),
) -> None: ) -> None:
"""This can only be done by the logged in user.""" """This can only be done by the logged in user."""
if not BaseUserApi.subclasses:
raise HTTPException(status_code=500, detail="Not implemented")
return await BaseUserApi.subclasses[0]().update_user(username, user) return await BaseUserApi.subclasses[0]().update_user(username, user)