python-fastapi improvements (#9649)

* [python-fastapi] Ignore some flake8 warnings

1. Some of the "imported but not used" warnings are there because
it is not easy to express what should be imported in mustache template
language. These warnings are silenced in order to keep the templates
morre readable.
2. Single quotes -> Double quotes (for consistency).

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

* [python-fastapi] Added flake8 config

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

* [python-fastapi] Set extra constraints on values

It is important to set all constraints (pattern, greater than, etc.)
on values of all arguments, because FastAPI can handle them automatically.

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

* [python-fastapi] Updated samples

Signed-off-by: Nikita Vakula <programmistov.programmist@gmail.com>
This commit is contained in:
Nikita Vakula 2021-06-03 04:13:24 +02:00 committed by GitHub
parent 2b495fc7a3
commit 93880a486e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 112 additions and 129 deletions

View File

@ -150,6 +150,7 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
supportingFiles.add(new SupportingFile("pyproject_toml.mustache", "", "pyproject.toml"));
supportingFiles.add(new SupportingFile("setup_cfg.mustache", "", "setup.cfg"));
supportingFiles.add(new SupportingFile(".flake8.mustache", "", ".flake8"));
}
@Override
@ -287,4 +288,10 @@ public class PythonFastAPIServerCodegen extends AbstractPythonCodegen {
System.out.println("# Please support his work directly via https://paypal.me/krjakbrjaki \uD83D\uDE4F #");
System.out.println("################################################################################");
}
@Override
public String toRegularExpression(String pattern) {
String regex = super.toRegularExpression(pattern);
return StringUtils.substring(regex, 1, -1);
}
}

View File

@ -0,0 +1,3 @@
[flake8]
max-line-length = 88
exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache,.venv

View File

@ -1,8 +1,8 @@
# coding: utf-8
from typing import Dict, List
from typing import Dict, List # noqa: F401
from fastapi import (
from fastapi import ( # noqa: F401
APIRouter,
Body,
Cookie,
@ -16,7 +16,7 @@ from fastapi import (
status,
)
from {{modelPackage}}.extra_models import TokenModel
from {{modelPackage}}.extra_models import TokenModel # noqa: F401
{{#imports}}
{{import}}
{{/imports}}
@ -53,7 +53,7 @@ async def {{operationId}}(
),
{{/authMethods}}
{{/hasAuthMethods}}
) -> {{#returnType}}{{.}}{{/returnType}}{{^returnType}}None{{/returnType}}: # noqa: E501
) -> {{#returnType}}{{.}}{{/returnType}}{{^returnType}}None{{/returnType}}:
{{#notes}}"""{{.}}"""
...{{/notes}}{{^notes}}...{{/notes}}
{{^-last}}

View File

@ -2,10 +2,11 @@
from fastapi.testclient import TestClient
import json
{{#vendorExtensions.x-skip-test}}
import pytest
{{/vendorExtensions.x-skip-test}}
{{#imports}}{{import}}
{{#imports}}{{import}} # noqa: F401
{{/imports}}
{{#operations}}
@ -23,24 +24,23 @@ def test_{{operationId}}(client: TestClient):
{{paramName}} = {{#isContainer}}[{{/isContainer}}{{{example}}}{{#isContainer}}]{{/isContainer}}
{{/bodyParam}}
{{#queryParams}}
{{#-first}}params = [{{/-first}}{{^-first}} {{/-first}}("{{paramName}}", {{{example}}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}}
{{/queryParams}}
headers = { {{#headerParams}}
'{{paramName}}': {{{example}}},{{/headerParams}}{{#authMethods}}
{{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}'{{name}}': 'special-key',{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'BasicZm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}
{{#-first}}params = [{{/-first}}("{{paramName}}", {{{example}}}){{^-last}}, {{/-last}}{{#-last}}]{{/-last}}{{/queryParams}}
headers = {{=<% %>=}}{<%#headerParams%><%={{ }}=%>
"{{paramName}}": {{{example}}},{{/headerParams}}{{#authMethods}}
{{#isOAuth}}"Authorization": "Bearer special-key",{{/isOAuth}}{{#isApiKey}}"{{name}}": "special-key",{{/isApiKey}}{{#isBasicBasic}}"Authorization": "BasicZm9vOmJhcg==",{{/isBasicBasic}}{{#isBasicBearer}}"Authorization": "Bearer special-key",{{/isBasicBearer}}{{/authMethods}}
}
{{#formParams}}
{{#-first}}
data = {
{{/-first}}
'{{paramName}}': {{{example}}}{{^-last}},{{/-last}}
"{{paramName}}": {{{example}}}{{^-last}},{{/-last}}
{{#-last}}
}
{{/-last}}
{{/formParams}}
response = client.request(
'{{httpMethod}}',
'{{{path}}}'{{#pathParams}}{{#-first}}.format({{/-first}}{{baseName}}={{{example}}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/pathParams}},
"{{httpMethod}}",
"{{{path}}}"{{#pathParams}}{{#-first}}.format({{/-first}}{{baseName}}={{{example}}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/pathParams}},
headers=headers,{{#bodyParam}}
json={{paramName}},{{/bodyParam}}{{#formParams}}{{#-first}}
data=data,{{/-first}}{{/formParams}}{{#queryParams}}{{#-first}}

View File

@ -1,6 +1,3 @@
import contextlib
from typing import Any
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

View File

@ -1 +1 @@
{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}} = {{#isPathParam}}Path{{/isPathParam}}{{#isHeaderParam}}Header{{/isHeaderParam}}{{#isFormParam}}Form{{/isFormParam}}{{#isQueryParam}}Query{{/isQueryParam}}{{#isCookieParam}}Cookie{{/isCookieParam}}{{#isBodyParam}}Body{{/isBodyParam}}(None, description="{{description}}")
{{#isPathParam}}{{baseName}}{{/isPathParam}}{{^isPathParam}}{{paramName}}{{/isPathParam}}: {{>param_type}} = {{#isPathParam}}Path{{/isPathParam}}{{#isHeaderParam}}Header{{/isHeaderParam}}{{#isFormParam}}Form{{/isFormParam}}{{#isQueryParam}}Query{{/isQueryParam}}{{#isCookieParam}}Cookie{{/isCookieParam}}{{#isBodyParam}}Body{{/isBodyParam}}({{#defaultValue}}{{.}}{{/defaultValue}}{{^defaultValue}}None{{/defaultValue}}, description="{{description}}"{{#isLong}}{{#minimum}}, ge={{minimum}}{{/minimum}}{{#maximum}}, le={{maximum}}{{/maximum}}{{/isLong}}{{#isInteger}}{{#minimum}}, ge={{minimum}}{{/minimum}}{{#maximum}}, le={{maximum}}{{/maximum}}{{/isInteger}}{{#pattern}}, regex=r"{{pattern}}"{{/pattern}}{{#minLength}}, min_length={{minLength}}{{/minLength}}{{#maxLength}}, max_length={{maxLength}}{{/maxLength}})

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
{{#models}}
{{#model}}
{{#pyImports}}

View File

@ -2,9 +2,9 @@
from typing import List
from fastapi import APIRouter, Depends, Response, Security, status
from fastapi.openapi.models import OAuthFlowImplicit, OAuthFlows
from fastapi.security import (
from fastapi import Depends, Security # noqa: F401
from fastapi.openapi.models import OAuthFlowImplicit, OAuthFlows # noqa: F401
from fastapi.security import ( # noqa: F401
HTTPAuthorizationCredentials,
HTTPBasic,
HTTPBasicCredentials,
@ -14,7 +14,7 @@ from fastapi.security import (
OAuth2PasswordBearer,
SecurityScopes,
)
from fastapi.security.api_key import APIKey, APIKeyCookie, APIKeyHeader, APIKeyQuery
from fastapi.security.api_key import APIKeyCookie, APIKeyHeader, APIKeyQuery # noqa: F401
from {{modelPackage}}.extra_models import TokenModel

View File

@ -0,0 +1,3 @@
[flake8]
max-line-length = 88
exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache,.venv

View File

@ -1,3 +1,4 @@
.flake8
.gitignore
Dockerfile
README.md

View File

@ -1,8 +1,8 @@
# coding: utf-8
from typing import Dict, List
from typing import Dict, List # noqa: F401
from fastapi import (
from fastapi import ( # noqa: F401
APIRouter,
Body,
Cookie,
@ -16,7 +16,7 @@ from fastapi import (
status,
)
from openapi_server.models.extra_models import TokenModel
from openapi_server.models.extra_models import TokenModel # noqa: F401
from openapi_server.models.api_response import ApiResponse
from openapi_server.models.pet import Pet
from openapi_server.security_api import get_token_petstore_auth, get_token_api_key
@ -34,12 +34,11 @@ router = APIRouter()
summary="Add a new pet to the store",
)
async def add_pet(
pet: Pet = Body(None, description="Pet object that needs to be added to the store")
,
pet: Pet = Body(None, description="Pet object that needs to be added to the store"),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["write:pets", "read:pets"]
),
) -> Pet: # noqa: E501
) -> Pet:
...
@ -52,14 +51,12 @@ async def add_pet(
summary="Deletes a pet",
)
async def delete_pet(
petId: int = Path(None, description="Pet id to delete")
,
api_key: str = Header(None, description="")
,
petId: int = Path(None, description="Pet id to delete"),
api_key: str = Header(None, description=""),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["write:pets", "read:pets"]
),
) -> None: # noqa: E501
) -> None:
...
@ -73,12 +70,11 @@ async def delete_pet(
summary="Finds Pets by status",
)
async def find_pets_by_status(
status: List[str] = Query(None, description="Status values that need to be considered for filter")
,
status: List[str] = Query(None, description="Status values that need to be considered for filter"),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["read:pets"]
),
) -> List[Pet]: # noqa: E501
) -> List[Pet]:
"""Multiple status values can be provided with comma separated strings"""
...
@ -93,12 +89,11 @@ async def find_pets_by_status(
summary="Finds Pets by tags",
)
async def find_pets_by_tags(
tags: List[str] = Query(None, description="Tags to filter by")
,
tags: List[str] = Query(None, description="Tags to filter by"),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["read:pets"]
),
) -> List[Pet]: # noqa: E501
) -> List[Pet]:
"""Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing."""
...
@ -114,12 +109,11 @@ async def find_pets_by_tags(
summary="Find pet by ID",
)
async def get_pet_by_id(
petId: int = Path(None, description="ID of pet to return")
,
petId: int = Path(None, description="ID of pet to return"),
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> Pet: # noqa: E501
) -> Pet:
"""Returns a single pet"""
...
@ -136,12 +130,11 @@ async def get_pet_by_id(
summary="Update an existing pet",
)
async def update_pet(
pet: Pet = Body(None, description="Pet object that needs to be added to the store")
,
pet: Pet = Body(None, description="Pet object that needs to be added to the store"),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["write:pets", "read:pets"]
),
) -> Pet: # noqa: E501
) -> Pet:
...
@ -154,16 +147,13 @@ async def update_pet(
summary="Updates a pet in the store with form data",
)
async def update_pet_with_form(
petId: int = Path(None, description="ID of pet that needs to be updated")
,
name: str = Form(None, description="Updated name of the pet")
,
status: str = Form(None, description="Updated status of the pet")
,
petId: int = Path(None, description="ID of pet that needs to be updated"),
name: str = Form(None, description="Updated name of the pet"),
status: str = Form(None, description="Updated status of the pet"),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["write:pets", "read:pets"]
),
) -> None: # noqa: E501
) -> None:
...
@ -176,14 +166,11 @@ async def update_pet_with_form(
summary="uploads an image",
)
async def upload_file(
petId: int = Path(None, description="ID of pet to update")
,
additional_metadata: str = Form(None, description="Additional data to pass to server")
,
file: str = Form(None, description="file to upload")
,
petId: int = Path(None, description="ID of pet to update"),
additional_metadata: str = Form(None, description="Additional data to pass to server"),
file: str = Form(None, description="file to upload"),
token_petstore_auth: TokenModel = Security(
get_token_petstore_auth, scopes=["write:pets", "read:pets"]
),
) -> ApiResponse: # noqa: E501
) -> ApiResponse:
...

View File

@ -1,8 +1,8 @@
# coding: utf-8
from typing import Dict, List
from typing import Dict, List # noqa: F401
from fastapi import (
from fastapi import ( # noqa: F401
APIRouter,
Body,
Cookie,
@ -16,7 +16,7 @@ from fastapi import (
status,
)
from openapi_server.models.extra_models import TokenModel
from openapi_server.models.extra_models import TokenModel # noqa: F401
from openapi_server.models.order import Order
from openapi_server.security_api import get_token_api_key
@ -33,9 +33,8 @@ router = APIRouter()
summary="Delete purchase order by ID",
)
async def delete_order(
orderId: str = Path(None, description="ID of the order that needs to be deleted")
,
) -> None: # noqa: E501
orderId: str = Path(None, description="ID of the order that needs to be deleted"),
) -> None:
"""For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors"""
...
@ -52,7 +51,7 @@ async def get_inventory(
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> Dict[str, int]: # noqa: E501
) -> Dict[str, int]:
"""Returns a map of status codes to quantities"""
...
@ -68,9 +67,8 @@ async def get_inventory(
summary="Find purchase order by ID",
)
async def get_order_by_id(
orderId: int = Path(None, description="ID of pet that needs to be fetched")
,
) -> Order: # noqa: E501
orderId: int = Path(None, description="ID of pet that needs to be fetched", ge=1, le=5),
) -> Order:
"""For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generated exceptions"""
...
@ -85,7 +83,6 @@ async def get_order_by_id(
summary="Place an order for a pet",
)
async def place_order(
order: Order = Body(None, description="order placed for purchasing the pet")
,
) -> Order: # noqa: E501
order: Order = Body(None, description="order placed for purchasing the pet"),
) -> Order:
...

View File

@ -1,8 +1,8 @@
# coding: utf-8
from typing import Dict, List
from typing import Dict, List # noqa: F401
from fastapi import (
from fastapi import ( # noqa: F401
APIRouter,
Body,
Cookie,
@ -16,7 +16,7 @@ from fastapi import (
status,
)
from openapi_server.models.extra_models import TokenModel
from openapi_server.models.extra_models import TokenModel # noqa: F401
from openapi_server.models.user import User
from openapi_server.security_api import get_token_api_key
@ -32,12 +32,11 @@ router = APIRouter()
summary="Create user",
)
async def create_user(
user: User = Body(None, description="Created user object")
,
user: User = Body(None, description="Created user object"),
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> None: # noqa: E501
) -> None:
"""This can only be done by the logged in user."""
...
@ -51,12 +50,11 @@ async def create_user(
summary="Creates list of users with given input array",
)
async def create_users_with_array_input(
user: List[User] = Body(None, description="List of user object")
,
user: List[User] = Body(None, description="List of user object"),
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> None: # noqa: E501
) -> None:
...
@ -69,12 +67,11 @@ async def create_users_with_array_input(
summary="Creates list of users with given input array",
)
async def create_users_with_list_input(
user: List[User] = Body(None, description="List of user object")
,
user: List[User] = Body(None, description="List of user object"),
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> None: # noqa: E501
) -> None:
...
@ -88,12 +85,11 @@ async def create_users_with_list_input(
summary="Delete user",
)
async def delete_user(
username: str = Path(None, description="The name that needs to be deleted")
,
username: str = Path(None, description="The name that needs to be deleted"),
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> None: # noqa: E501
) -> None:
"""This can only be done by the logged in user."""
...
@ -109,9 +105,8 @@ async def delete_user(
summary="Get user by user name",
)
async def get_user_by_name(
username: str = Path(None, description="The name that needs to be fetched. Use user1 for testing.")
,
) -> User: # noqa: E501
username: str = Path(None, description="The name that needs to be fetched. Use user1 for testing."),
) -> User:
...
@ -125,11 +120,9 @@ async def get_user_by_name(
summary="Logs user into the system",
)
async def login_user(
username: str = Query(None, description="The user name for login")
,
password: str = Query(None, description="The password for login in clear text")
,
) -> str: # noqa: E501
username: str = Query(None, description="The user name for login", regex=r"^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$"),
password: str = Query(None, description="The password for login in clear text"),
) -> str:
...
@ -145,7 +138,7 @@ async def logout_user(
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> None: # noqa: E501
) -> None:
...
@ -159,13 +152,11 @@ async def logout_user(
summary="Updated user",
)
async def update_user(
username: str = Path(None, description="name that need to be deleted")
,
user: User = Body(None, description="Updated user object")
,
username: str = Path(None, description="name that need to be deleted"),
user: User = Body(None, description="Updated user object"),
token_api_key: TokenModel = Security(
get_token_api_key
),
) -> None: # noqa: E501
) -> None:
"""This can only be done by the logged in user."""
...

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
class ApiResponse(BaseModel):

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
class Category(BaseModel):

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
class Order(BaseModel):

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
from openapi_server.models.category import Category
from openapi_server.models.tag import Tag

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
class Tag(BaseModel):

View File

@ -1,10 +1,10 @@
# coding: utf-8
from datetime import date, datetime
from datetime import date, datetime # noqa: F401
from typing import Dict, List, Optional
from typing import Dict, List, Optional # noqa: F401
from pydantic import BaseModel, EmailStr, validator
from pydantic import BaseModel, EmailStr, validator # noqa: F401
class User(BaseModel):

View File

@ -2,9 +2,9 @@
from typing import List
from fastapi import APIRouter, Depends, Response, Security, status
from fastapi.openapi.models import OAuthFlowImplicit, OAuthFlows
from fastapi.security import (
from fastapi import Depends, Security # noqa: F401
from fastapi.openapi.models import OAuthFlowImplicit, OAuthFlows # noqa: F401
from fastapi.security import ( # noqa: F401
HTTPAuthorizationCredentials,
HTTPBasic,
HTTPBasicCredentials,
@ -14,7 +14,7 @@ from fastapi.security import (
OAuth2PasswordBearer,
SecurityScopes,
)
from fastapi.security.api_key import APIKey, APIKeyCookie, APIKeyHeader, APIKeyQuery
from fastapi.security.api_key import APIKeyCookie, APIKeyHeader, APIKeyQuery # noqa: F401
from openapi_server.models.extra_models import TokenModel

View File

@ -1,6 +1,3 @@
import contextlib
from typing import Any
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient