[python-fastapi] auto generate impl folder (#17852)

* auto generate impl folder

* fix working dir

* install pytest

* add new file

* update, fix

* test with py 3.9

* fix tests

* update doc
This commit is contained in:
William Cheng 2024-02-15 13:08:04 +08:00 committed by GitHub
parent b0c9456add
commit 4810dd52c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 192 additions and 131 deletions

View File

@ -0,0 +1,33 @@
name: Python FastAPI Server
on:
push:
paths:
- samples/server/petstore/python-fastapi/**
pull_request:
paths:
- samples/server/petstore/python-fastapi/**
jobs:
build:
name: Test Python FastAPI server
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sample:
# servers
- samples/server/petstore/python-fastapi/
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install dependencies
working-directory: ${{ matrix.sample }}
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest
- name: Test
working-directory: ${{ matrix.sample }}
run: PYTHONPATH=src pytest

View File

@ -23,7 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true| |disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true| |ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|<dl><dt>**false**</dt><dd>No changes to the enum's are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false| |enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|<dl><dt>**false**</dt><dd>No changes to the enum's are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false|
|fastapiImplementationPackage|python package name for the implementation code (convention: snake_case).| |openapi_server.impl| |fastapiImplementationPackage|python package name for the implementation code (convention: snake_case).| |impl|
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true| |legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|packageName|python package name (convention: snake_case).| |openapi_server| |packageName|python package name (convention: snake_case).| |openapi_server|
|packageVersion|python package version.| |1.0.0| |packageVersion|python package version.| |1.0.0|

View File

@ -126,7 +126,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
apiPackage = "apis"; apiPackage = "apis";
modelPackage = "models"; modelPackage = "models";
testPackage = "tests"; testPackage = "tests";
implPackage = DEFAULT_PACKAGE_NAME.concat(".impl"); implPackage = "impl";
apiTestTemplateFiles().put("api_test.mustache", ".py"); apiTestTemplateFiles().put("api_test.mustache", ".py");
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).") cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "python package name (convention: snake_case).")
@ -138,7 +138,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, "directory for generated python source code") cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, "directory for generated python source code")
.defaultValue(DEFAULT_SOURCE_FOLDER)); .defaultValue(DEFAULT_SOURCE_FOLDER));
cliOptions.add(new CliOption(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, "python package name for the implementation code (convention: snake_case).") cliOptions.add(new CliOption(CodegenConstants.FASTAPI_IMPLEMENTATION_PACKAGE, "python package name for the implementation code (convention: snake_case).")
.defaultValue(DEFAULT_PACKAGE_NAME.concat(".impl"))); .defaultValue(implPackage));
} }
@ -178,6 +178,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
} }
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(modelFileFolder(), outputFolder), "__init__.py")); supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(modelFileFolder(), outputFolder), "__init__.py"));
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiFileFolder(), outputFolder), "__init__.py")); supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiFileFolder(), outputFolder), "__init__.py"));
supportingFiles.add(new SupportingFile("__init__.mustache", StringUtils.substringAfter(apiImplFileFolder(), outputFolder), "__init__.py"));
supportingFiles.add(new SupportingFile("conftest.mustache", testPackage.replace('.', File.separatorChar), "conftest.py")); supportingFiles.add(new SupportingFile("conftest.mustache", testPackage.replace('.', File.separatorChar), "conftest.py"));
@ -304,6 +305,10 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
return String.join(File.separator, new String[]{outputFolder, sourceFolder, apiPackage().replace('.', File.separatorChar)}); return String.join(File.separator, new String[]{outputFolder, sourceFolder, apiPackage().replace('.', File.separatorChar)});
} }
public String apiImplFileFolder() {
return String.join(File.separator, new String[]{outputFolder, sourceFolder, implPackage.replace('.', File.separatorChar)});
}
@Override @Override
public String modelFileFolder() { public String modelFileFolder() {
return String.join(File.separator, new String[]{outputFolder, sourceFolder, modelPackage().replace('.', File.separatorChar)}); return String.join(File.separator, new String[]{outputFolder, sourceFolder, modelPackage().replace('.', File.separatorChar)});

View File

@ -38,14 +38,15 @@ def test_{{operationId}}(client: TestClient):
} }
{{/-last}} {{/-last}}
{{/formParams}} {{/formParams}}
response = client.request( # uncomment below to make a request
"{{httpMethod}}", #response = client.request(
"{{{path}}}"{{#pathParams}}{{#-first}}.format({{/-first}}{{baseName}}={{{example}}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/pathParams}}, # "{{httpMethod}}",
headers=headers,{{#bodyParam}} # "{{{path}}}"{{#pathParams}}{{#-first}}.format({{/-first}}{{baseName}}={{{example}}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/pathParams}},
json={{paramName}},{{/bodyParam}}{{#formParams}}{{#-first}} # headers=headers,{{#bodyParam}}
data=data,{{/-first}}{{/formParams}}{{#queryParams}}{{#-first}} # json={{paramName}},{{/bodyParam}}{{#formParams}}{{#-first}}
params=params,{{/-first}}{{/queryParams}} # data=data,{{/-first}}{{/formParams}}{{#queryParams}}{{#-first}}
) # params=params,{{/-first}}{{/queryParams}}
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200

View File

@ -31,6 +31,6 @@ typing-extensions==3.10.0.0
ujson==4.0.2 ujson==4.0.2
urllib3==1.26.5 urllib3==1.26.5
uvicorn==0.13.4 uvicorn==0.13.4
uvloop==0.14.0 uvloop==0.19.0
watchgod==0.7 watchgod==0.7
websockets==10.0 websockets==10.0

View File

@ -16,6 +16,7 @@ src/openapi_server/apis/store_api.py
src/openapi_server/apis/store_api_base.py src/openapi_server/apis/store_api_base.py
src/openapi_server/apis/user_api.py src/openapi_server/apis/user_api.py
src/openapi_server/apis/user_api_base.py src/openapi_server/apis/user_api_base.py
src/openapi_server/impl/__init__.py
src/openapi_server/main.py src/openapi_server/main.py
src/openapi_server/models/__init__.py src/openapi_server/models/__init__.py
src/openapi_server/models/api_response.py src/openapi_server/models/api_response.py

View File

@ -31,6 +31,6 @@ typing-extensions==3.10.0.0
ujson==4.0.2 ujson==4.0.2
urllib3==1.26.5 urllib3==1.26.5
uvicorn==0.13.4 uvicorn==0.13.4
uvloop==0.14.0 uvloop==0.19.0
watchgod==0.7 watchgod==0.7
websockets==10.0 websockets==10.0

View File

@ -13,12 +13,13 @@ def test_fake_query_param_default(client: TestClient):
params = [("has_default", 'Hello World'), ("no_default", 'no_default_example')] params = [("has_default", 'Hello World'), ("no_default", 'no_default_example')]
headers = { headers = {
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/fake/query_param_default", # "GET",
headers=headers, # "/fake/query_param_default",
params=params, # headers=headers,
) # params=params,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200

View File

@ -17,12 +17,13 @@ def test_add_pet(client: TestClient):
headers = { headers = {
"Authorization": "Bearer special-key", "Authorization": "Bearer special-key",
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/pet", # "POST",
headers=headers, # "/pet",
json=pet, # headers=headers,
) # json=pet,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -38,11 +39,12 @@ def test_delete_pet(client: TestClient):
"api_key": 'api_key_example', "api_key": 'api_key_example',
"Authorization": "Bearer special-key", "Authorization": "Bearer special-key",
} }
response = client.request( # uncomment below to make a request
"DELETE", #response = client.request(
"/pet/{petId}".format(petId=56), # "DELETE",
headers=headers, # "/pet/{petId}".format(petId=56),
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -57,12 +59,13 @@ def test_find_pets_by_status(client: TestClient):
headers = { headers = {
"Authorization": "Bearer special-key", "Authorization": "Bearer special-key",
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/pet/findByStatus", # "GET",
headers=headers, # "/pet/findByStatus",
params=params, # headers=headers,
) # params=params,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -77,12 +80,13 @@ def test_find_pets_by_tags(client: TestClient):
headers = { headers = {
"Authorization": "Bearer special-key", "Authorization": "Bearer special-key",
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/pet/findByTags", # "GET",
headers=headers, # "/pet/findByTags",
params=params, # headers=headers,
) # params=params,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -97,11 +101,12 @@ def test_get_pet_by_id(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/pet/{petId}".format(petId=56), # "GET",
headers=headers, # "/pet/{petId}".format(petId=56),
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -117,12 +122,13 @@ def test_update_pet(client: TestClient):
headers = { headers = {
"Authorization": "Bearer special-key", "Authorization": "Bearer special-key",
} }
response = client.request( # uncomment below to make a request
"PUT", #response = client.request(
"/pet", # "PUT",
headers=headers, # "/pet",
json=pet, # headers=headers,
) # json=pet,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -141,12 +147,13 @@ def test_update_pet_with_form(client: TestClient):
"name": 'name_example', "name": 'name_example',
"status": 'status_example' "status": 'status_example'
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/pet/{petId}".format(petId=56), # "POST",
headers=headers, # "/pet/{petId}".format(petId=56),
data=data, # headers=headers,
) # data=data,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -165,12 +172,13 @@ def test_upload_file(client: TestClient):
"additional_metadata": 'additional_metadata_example', "additional_metadata": 'additional_metadata_example',
"file": '/path/to/file' "file": '/path/to/file'
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/pet/{petId}/uploadImage".format(petId=56), # "POST",
headers=headers, # "/pet/{petId}/uploadImage".format(petId=56),
data=data, # headers=headers,
) # data=data,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200

View File

@ -14,11 +14,12 @@ def test_delete_order(client: TestClient):
headers = { headers = {
} }
response = client.request( # uncomment below to make a request
"DELETE", #response = client.request(
"/store/order/{orderId}".format(orderId='order_id_example'), # "DELETE",
headers=headers, # "/store/order/{orderId}".format(orderId='order_id_example'),
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -33,11 +34,12 @@ def test_get_inventory(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/store/inventory", # "GET",
headers=headers, # "/store/inventory",
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -51,11 +53,12 @@ def test_get_order_by_id(client: TestClient):
headers = { headers = {
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/store/order/{orderId}".format(orderId=56), # "GET",
headers=headers, # "/store/order/{orderId}".format(orderId=56),
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -70,12 +73,13 @@ def test_place_order(client: TestClient):
headers = { headers = {
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/store/order", # "POST",
headers=headers, # "/store/order",
json=order, # headers=headers,
) # json=order,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200

View File

@ -16,12 +16,13 @@ def test_create_user(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/user", # "POST",
headers=headers, # "/user",
json=user, # headers=headers,
) # json=user,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -37,12 +38,13 @@ def test_create_users_with_array_input(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/user/createWithArray", # "POST",
headers=headers, # "/user/createWithArray",
json=user, # headers=headers,
) # json=user,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -58,12 +60,13 @@ def test_create_users_with_list_input(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"POST", #response = client.request(
"/user/createWithList", # "POST",
headers=headers, # "/user/createWithList",
json=user, # headers=headers,
) # json=user,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -78,11 +81,12 @@ def test_delete_user(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"DELETE", #response = client.request(
"/user/{username}".format(username='username_example'), # "DELETE",
headers=headers, # "/user/{username}".format(username='username_example'),
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -96,11 +100,12 @@ def test_get_user_by_name(client: TestClient):
headers = { headers = {
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/user/{username}".format(username='username_example'), # "GET",
headers=headers, # "/user/{username}".format(username='username_example'),
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -114,12 +119,13 @@ def test_login_user(client: TestClient):
params = [("username", 'username_example'), ("password", 'password_example')] params = [("username", 'username_example'), ("password", 'password_example')]
headers = { headers = {
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/user/login", # "GET",
headers=headers, # "/user/login",
params=params, # headers=headers,
) # params=params,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -134,11 +140,12 @@ def test_logout_user(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"GET", #response = client.request(
"/user/logout", # "GET",
headers=headers, # "/user/logout",
) # headers=headers,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200
@ -154,12 +161,13 @@ def test_update_user(client: TestClient):
headers = { headers = {
"api_key": "special-key", "api_key": "special-key",
} }
response = client.request( # uncomment below to make a request
"PUT", #response = client.request(
"/user/{username}".format(username='username_example'), # "PUT",
headers=headers, # "/user/{username}".format(username='username_example'),
json=user, # headers=headers,
) # json=user,
#)
# uncomment below to assert the status code of the HTTP response # uncomment below to assert the status code of the HTTP response
#assert response.status_code == 200 #assert response.status_code == 200